docs: add Astro how-to reference from crash course analysis #6
52
docs/astro-howto/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Astro How-To — Project Index
|
||||
|
||||
Local documentation generated from the Astro Web Framework Crash Course
|
||||
by James Q Quick (freeCodeCamp, 1h 16m).
|
||||
|
||||
## How This Was Built
|
||||
|
||||
1. **Audio extracted** from source video (ffmpeg, 16kHz mono WAV, 141 MB)
|
||||
2. **Transcribed locally** with faster-whisper base model (CPU, 8.7× realtime)
|
||||
3. **Structured by GLM-5-turbo** into topics, steps, commands, and screenshot candidates
|
||||
4. **Contact sheet generated** (77 frames, one per 60s, 5-column grid)
|
||||
|
||||
Zero API keys used for media processing. All local.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [transcript_local.txt](transcript_local.txt) | Full local transcript (805 lines, timestamped) |
|
||||
| [transcript.txt](transcript.txt) | YouTube transcript (fallback reference) |
|
||||
| [docs/SUMMARY.md](docs/SUMMARY.md) | Course overview with all 33 topics |
|
||||
| [docs/OUTLINE.md](docs/OUTLINE.md) | Timestamped topic index |
|
||||
| [docs/HOWTO.md](docs/HOWTO.md) | 15-step how-to guide + key concepts |
|
||||
| [docs/COMMANDS.md](docs/COMMANDS.md) | CLI commands, extensions, packages |
|
||||
| [docs/QUESTIONS.md](docs/QUESTIONS.md) | Open questions and unclear sections |
|
||||
| [docs/SCREENSHOTS.md](docs/SCREENSHOTS.md) | 17 recommended frame capture points |
|
||||
| [contact-sheet/contact_sheet.jpg](contact-sheet/contact_sheet.jpg) | 77-frame grid (2408×4350px, 2.8 MB) |
|
||||
| [contact-sheet/report.json](contact-sheet/report.json) | Contact sheet generation report |
|
||||
| [screenshots/index.md](screenshots/index.md) | 17 targeted screenshots index |
|
||||
| [screenshots/report.json](screenshots/report.json) | Screenshot extraction report |
|
||||
| [run_manifest.json](run_manifest.json) | Machine-readable run summary |
|
||||
| [artifacts.sha256](artifacts.sha256) | Checksums for key outputs |
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| [scripts/generate_contact_sheet.py](scripts/generate_contact_sheet.py) | Contact sheet generator (OpenCV + Pillow) |
|
||||
| [scripts/transcribe_local.py](scripts/transcribe_local.py) | Local transcription (faster-whisper) |
|
||||
| [scripts/extract_screenshots.sh](scripts/extract_screenshots.sh) | Targeted frame extraction |
|
||||
|
||||
## Source
|
||||
|
||||
Video: /home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4
|
||||
Size: 450 MB | Codec: vp9 1080p | Duration: 1:16:47 | Frames: 138,228
|
||||
Original: untouched (read-only access only)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [x] Extract targeted screenshots from SCREENSHOTS.md (17 frames, 3.2 MB)
|
||||
- [ ] Consider faster-whisper small/medium for better transcript quality
|
||||
- [ ] Use this as base for Clawdie CMS skill
|
||||
30
docs/astro-howto/artifacts.sha256
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
982de689f99ba79bb3d5f118894d5857ce9d5fe678da91211f21402ce1549543 transcript_local.txt
|
||||
3e120745f2244506a9fd784adddab3b5a8a6b0a02192fa0b9f5c29d91de257da docs/COMMANDS.md
|
||||
80699244254dcf48b58a04ef74a1c026c6ef9e9f47f186179097e0409806c1e1 docs/HOWTO.md
|
||||
e9a0e30e444fdf30d05e60be4c49ea159b88c4d6bac094640334a50a0ec742af docs/OUTLINE.md
|
||||
b8fd24369c2d2b44879579aacef52e924dde87c6484088d0c18df8e202a6d715 docs/QUESTIONS.md
|
||||
10683c95bec327161459f332b5e4c29bd51f21f27adaacfd9d8e1fae8c5cf84b docs/SCREENSHOTS.md
|
||||
c453000a320de466052b81a258514c88e259cf7d5b60257eddf883bf49b33ed4 docs/SUMMARY.md
|
||||
f397c075ea0eef43dabfcbe1417ba1b3edd5d5cead290888d1080658ae989d90 contact-sheet/contact_sheet.jpg
|
||||
46a40dc690775c171e61cee43befde037e33842cffcdb89d89356805654f667e contact-sheet/report.json
|
||||
e81b5cc8f4984a560f157f80172101f021c0b87cd99aa85653f1ec799aa1be20 run_manifest.json
|
||||
865605c167527ac5574b94ed9e3538efae4b9e628768592467e009dd8b9784cc README.md
|
||||
fa47355b542c9d7ac0a2adacbe249cc4fd2eb23148e0e5cc75296fd9f9e638f1 screenshots/001_00-01-05_astro.new_website_showing_starter_template_options.jpg
|
||||
14147682d20f9b178b3e64e45c2507a33339697d41d5b9d5726dd6f5f9abc37d screenshots/002_00-01-25_documentation_search_modal_opened_with_command+k.jpg
|
||||
55af0d5e12e36bd625ab570273f412b45fde062c5cde68bedd375792598929fd screenshots/003_00-02-39_animated_astro_project_creation_wizard_in_terminal.jpg
|
||||
c7c1e2f7895b3e88020e1532eed0c44fb86b0a72513e7d2d1c8a34ee5122091d screenshots/004_00-03-00_houston_mascot_exit_animation_after_project_creation.jpg
|
||||
9e0fe78f8fe0f91ef434eaefa9feb7b35a103fcc4aacbff3938edcf2bf1123d3 screenshots/005_00-04-10_starter_application_at_localhost:4321.jpg
|
||||
c15616f3480936157efab008df8f1c95d5e76cef88ccf0a83b32da2fb1b34b00 screenshots/006_00-08-38_vs_code_.astro_file_without_extension_showing_as_plain_text.jpg
|
||||
8d05b01f54df4c246668c3369be4be58630d28fdf8e044523a1b9811bbe90986 screenshots/007_00-09-00_vs_code_.astro_file_with_astro_extension_showing_syntax_high.jpg
|
||||
d6715f20d18d1a884bdd0008a64a7a50815833e8326e91b520fa1aca2b22c9d3 screenshots/008_00-10-17_astro.config.mjs_showing_tailwind()_in_integrations_array.jpg
|
||||
a513e292082c55daa11a0a684debaa9bdcf228765589accaca992e7f7a4d974d screenshots/009_00-31-33_blog_listing_page_with_grid_of_post_cards.jpg
|
||||
cc984dcd88af14110852a3c586ddb1f522debc67bf33a2c1c26411067caa7df4 screenshots/010_00-34-20_individual_blog_post_page_with_rendered_markdown.jpg
|
||||
36feb459535eb2e2e439398e195a608ebbfc0f4f25a696b89148aece74056116 screenshots/011_00-51-01_browser_network_tab_showing_webp_images.jpg
|
||||
8b0951795a815198446f857d97aa254849414f4f753f17db1f54b2b32bd8df06 screenshots/012_00-52-43_view_transitions_animation_between_pages.jpg
|
||||
2158295d3643173e616ef584749561162b550e5bce055b856256586ffe81dcfa screenshots/013_00-54-10_mdx_file_with_inline_components.jpg
|
||||
36ba3db82b03d8830f3e29eefb610c2b121db58dabacaf631cbbebe32ee7b70b screenshots/014_00-57-58_netlify_deploy_dashboard_showing_successful_build.jpg
|
||||
b1ee2f3e1699c72d916f6b5b66f05fef468438ac38156048f4f31c7099c04df3 screenshots/015_00-58-50_vercel_deploy_dashboard.jpg
|
||||
f1bf4a879fd507d3315caeb9e1540e849b823f131481d1b9e78b285d61c648f8 screenshots/016_01-05-17_api_endpoint_returning_json_in_browser.jpg
|
||||
f7cac0aa05747c741e2da053c6ec4fa1610accf4f95c27e1ddf6281eab503173 screenshots/017_01-09-17_ssr_individual_post_page_loading_dynamically.jpg
|
||||
a237b6dfb5a6d2f574bf68622f74397fd4957e10ee2087aaaca6eb452492199c screenshots/index.md
|
||||
e00e12cbd2b9fed7a422d68629e6d3bf992a502046261dd7dfc895ba1f3eb5fa screenshots/report.json
|
||||
BIN
docs/astro-howto/contact-sheet/contact_sheet.jpg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
143
docs/astro-howto/contact-sheet/generate_contact_sheet.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import math
|
||||
import argparse
|
||||
import threading
|
||||
import cv2
|
||||
from PIL import Image
|
||||
|
||||
def read_with_timeout(cap, timeout=5.0):
|
||||
result = [None, None]
|
||||
exception = [None]
|
||||
def target():
|
||||
try:
|
||||
result[0], result[1] = cap.read()
|
||||
except Exception as e:
|
||||
exception[0] = e
|
||||
result[0] = False
|
||||
thread = threading.Thread(target=target)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
thread.join(timeout=timeout)
|
||||
if thread.is_alive():
|
||||
return False, None
|
||||
if exception[0]:
|
||||
return False, None
|
||||
return result[0], result[1]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate a video contact sheet.")
|
||||
parser.add_argument("--save-frames", action="store_true", help="Save individual frames")
|
||||
args = parser.parse_args()
|
||||
|
||||
video_path = "/home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4"
|
||||
output_dir = "/home/samob/ai/astro-howto/contact-sheet/"
|
||||
frames_dir = os.path.join(output_dir, "contact_sheet_frames")
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
if args.save_frames:
|
||||
os.makedirs(frames_dir, exist_ok=True)
|
||||
|
||||
if not os.path.exists(video_path):
|
||||
print(f"Error: Source video not found at {video_path}")
|
||||
sys.exit(1)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
if not cap.isOpened():
|
||||
print("Error: Could not open video file.")
|
||||
sys.exit(1)
|
||||
|
||||
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
interval_frames = int(60 * fps)
|
||||
|
||||
targets = list(range(0, total_frames, interval_frames))
|
||||
|
||||
thumbnails = []
|
||||
skipped = 0
|
||||
count = 0
|
||||
|
||||
for target_pos in targets:
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, target_pos)
|
||||
ret, frame = read_with_timeout(cap, timeout=5.0)
|
||||
|
||||
if not ret or frame is None:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
h, w = frame_rgb.shape[:2]
|
||||
new_w = 480
|
||||
new_h = int((new_w / w) * h)
|
||||
resized = cv2.resize(frame_rgb, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
||||
|
||||
pil_img = Image.fromarray(resized)
|
||||
|
||||
if args.save_frames:
|
||||
frame_filename = f"frame_{count:03d}.jpg"
|
||||
pil_img.save(os.path.join(frames_dir, frame_filename), quality=90)
|
||||
|
||||
thumbnails.append(pil_img)
|
||||
count += 1
|
||||
except Exception:
|
||||
skipped += 1
|
||||
|
||||
cap.release()
|
||||
|
||||
if not thumbnails:
|
||||
print("Error: No frames could be extracted.")
|
||||
sys.exit(1)
|
||||
|
||||
cols = 5
|
||||
thumb_w, thumb_h = thumbnails[0].size
|
||||
padding = 2
|
||||
rows = math.ceil(len(thumbnails) / cols)
|
||||
|
||||
sheet_w = cols * thumb_w + (cols - 1) * padding
|
||||
sheet_h = rows * thumb_h + (rows - 1) * padding
|
||||
|
||||
sheet = Image.new('RGB', (sheet_w, sheet_h), (255, 255, 255))
|
||||
|
||||
for i, img in enumerate(thumbnails):
|
||||
r = i // cols
|
||||
c = i % cols
|
||||
x = c * (thumb_w + padding)
|
||||
y = r * (thumb_h + padding)
|
||||
sheet.paste(img, (x, y))
|
||||
|
||||
output_path = os.path.join(output_dir, "contact_sheet.jpg")
|
||||
sheet.save(output_path, quality=95)
|
||||
|
||||
extraction_time = time.time() - start_time
|
||||
|
||||
report = {
|
||||
"frame_count": count,
|
||||
"path": output_path,
|
||||
"dimensions_px": [sheet_w, sheet_h],
|
||||
"skipped_frames": skipped,
|
||||
"extraction_time_s": round(extraction_time, 2)
|
||||
}
|
||||
|
||||
report_path = os.path.join(output_dir, "report.json")
|
||||
with open(report_path, 'w') as f:
|
||||
json.dump(report, f, indent=4)
|
||||
|
||||
if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
|
||||
print("Error: Output validation failed - file missing or empty.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Frames extracted: {count}")
|
||||
print(f"Skipped: {skipped}")
|
||||
print(f"Output path: {output_path}")
|
||||
print(f"Dimensions: {sheet_w}x{sheet_h}px")
|
||||
print(f"Time: {report['extraction_time_s']}s")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
docs/astro-howto/contact-sheet/report.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"frame_count": 77,
|
||||
"path": "/home/samob/ai/astro-howto/contact-sheet/contact_sheet.jpg",
|
||||
"dimensions_px": [
|
||||
2408,
|
||||
4350
|
||||
],
|
||||
"skipped_frames": 0,
|
||||
"extraction_time_s": 7.49
|
||||
}
|
||||
45
docs/astro-howto/docs/COMMANDS.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Astro — Commands & Tools Reference
|
||||
|
||||
## CLI Commands
|
||||
|
||||
- `code -r`
|
||||
- `git init`
|
||||
- `git push`
|
||||
- `git remote add origin`
|
||||
- `npm create astro@latest`
|
||||
- `npm install -D @tailwindcss/typography`
|
||||
- `npm run dev`
|
||||
- `npx astro add mdx`
|
||||
- `npx astro add netlify`
|
||||
- `npx astro add tailwind`
|
||||
- `npx astro add vercel`
|
||||
- `nvm install 20`
|
||||
- `nvm use 18`
|
||||
|
||||
## VS Code Extensions
|
||||
|
||||
| Extension | Purpose |
|
||||
|-----------|---------|
|
||||
| Astro (official) | Syntax highlighting, IntelliSense for .astro files |
|
||||
| Astro Snippets | Code snippets for faster component creation |
|
||||
| Houston | Theme + animated mascot icon in file explorer |
|
||||
| Tailwind CSS IntelliSense | Autocomplete for Tailwind classes |
|
||||
|
||||
## Key Packages
|
||||
|
||||
| Package | Use |
|
||||
|---------|-----|
|
||||
| `astro` | Core framework |
|
||||
| `@astrojs/tailwind` | Tailwind CSS integration |
|
||||
| `@astrojs/mdx` | MDX support (JSX in Markdown) |
|
||||
| `@astrojs/netlify` | Netlify SSR adapter (serverless functions) |
|
||||
| `@astrojs/vercel` | Vercel SSR adapter (serverless functions) |
|
||||
| `@tailwindcss/typography` | Markdown content prose styling |
|
||||
| `zod` | Schema validation for content collections |
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| Cmd+K (Mac) / Ctrl+K (Windows) | Open docs search modal |
|
||||
| Cmd+Shift+F (Mac) / Ctrl+Shift+F (Windows) | Find and replace across files |
|
||||
140
docs/astro-howto/docs/HOWTO.md
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
# Astro — Step-by-Step How-To Guide
|
||||
|
||||
Based on the freeCodeCamp crash course by James Q Quick.
|
||||
|
||||
## 1. Create New Project
|
||||
|
||||
From the Astro docs getting started page. Choose folder name, sample files,
|
||||
Yes to dependencies, TypeScript (strict/recommended), Yes to Git.
|
||||
|
||||
`npm create astro@latest`
|
||||
|
||||
Requires Node.js 18+. Use NVM to manage:
|
||||
|
||||
`nvm use 18`
|
||||
`nvm install 20`
|
||||
|
||||
## 2. Open in VS Code
|
||||
|
||||
`code -r <project-folder>`
|
||||
|
||||
## 3. Start Dev Server
|
||||
|
||||
Runs on http://localhost:4321
|
||||
|
||||
`npm run dev`
|
||||
|
||||
## 4. Add Tailwind CSS
|
||||
|
||||
Auto-generates tailwind.config and updates astro.config.mjs.
|
||||
|
||||
`npx astro add tailwind`
|
||||
|
||||
## 5. Create Content Config
|
||||
|
||||
`src/content/config.ts` with `defineCollection` and Zod schema for your
|
||||
content type (e.g., posts with author, date, image, title).
|
||||
|
||||
## 6. Query Posts
|
||||
|
||||
```ts
|
||||
import { getCollection } from 'astro:content';
|
||||
const posts = await getCollection('posts');
|
||||
```
|
||||
|
||||
## 7. Dynamic Routes
|
||||
|
||||
`pages/blog/[slug].astro` + export `getStaticPaths` function that returns
|
||||
`{params: {slug}, props: {post}}` for each page.
|
||||
|
||||
## 8. Render Markdown Content
|
||||
|
||||
```ts
|
||||
const { Content } = await post.render();
|
||||
// In template: <Content />
|
||||
```
|
||||
|
||||
## 9. Install Typography Plugin
|
||||
|
||||
For proper Markdown content styling:
|
||||
|
||||
`npm install -D @tailwindcss/typography`
|
||||
|
||||
Add to tailwind.config plugins array.
|
||||
|
||||
## 10. Use Astro Image Component
|
||||
|
||||
```ts
|
||||
import { Image } from 'astro:assets';
|
||||
```
|
||||
Auto-converts to webp, resizes, lazy loads. Much smaller than raw images.
|
||||
|
||||
## 11. Add View Transitions
|
||||
|
||||
```ts
|
||||
import { ViewTransitions } from 'astro:transitions';
|
||||
```
|
||||
Add `<ViewTransitions />` to layout head. One line for smooth page animations.
|
||||
|
||||
## 12. Add MDX Support
|
||||
|
||||
Enables .mdx files with inline JSX components and exported variables.
|
||||
|
||||
`npx astro add mdx`
|
||||
|
||||
## 13. Initialize Git & Push
|
||||
|
||||
`git init && git add . && git commit -m 'init'`
|
||||
`git remote add origin <github-url> && git push -u origin main`
|
||||
|
||||
## 14. Deploy Static Site
|
||||
|
||||
- **Netlify:** Import GitHub repo → auto-detects Astro build
|
||||
- **Vercel:** Import GitHub repo → auto-detects Astro settings
|
||||
|
||||
## 15. Deploy SSR
|
||||
|
||||
Add the appropriate adapter:
|
||||
|
||||
`npx astro add netlify` (deploys as Netlify Functions)
|
||||
`npx astro add vercel` (deploys as serverless functions)
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### File-Based Routing
|
||||
|
||||
Files in `src/pages/` map directly to URL routes:
|
||||
- `index.astro` → `/`
|
||||
- `blog.astro` → `/blog`
|
||||
- `blog/[slug].astro` → `/blog/post-title`
|
||||
|
||||
### Component Structure
|
||||
|
||||
```
|
||||
---
|
||||
// Frontmatter: JS/TypeScript, imports, logic
|
||||
---
|
||||
<!-- Template: HTML, markdown, components -->
|
||||
```
|
||||
|
||||
### Content Collections
|
||||
|
||||
`src/content/config.ts` defines schemas. Query with `getCollection()`.
|
||||
|
||||
### SSG vs SSR vs Hybrid
|
||||
|
||||
- **SSG** (default): HTML at build time, served from CDN
|
||||
- **SSR**: HTML per request, can query databases, check auth
|
||||
- **Hybrid**: Set `output: 'hybrid'`, add `export const prerender = true` for static pages
|
||||
|
||||
### API Endpoints
|
||||
|
||||
Create `.ts` files in `pages/api/` exporting `GET`/`POST` handlers.
|
||||
Full backend capability — form submissions, JSON APIs, auth checks.
|
||||
|
||||
## Deployment Matrix
|
||||
|
||||
| Host | Static | SSR |
|
||||
|------|--------|-----|
|
||||
| Netlify | Import repo | `npx astro add netlify` |
|
||||
| Vercel | Import repo | `npx astro add vercel` |
|
||||
39
docs/astro-howto/docs/OUTLINE.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Astro Crash Course — Timestamped Outline
|
||||
|
||||
| Timestamp | Topic |
|
||||
|-----------|-------|
|
||||
| 00:00 | Introduction to Astro |
|
||||
| 00:29 | Course Overview |
|
||||
| 01:05 | Project Creation |
|
||||
| 02:01 | Node.js Requirement |
|
||||
| 03:38 | Opening in VS Code |
|
||||
| 03:42 | Dev Server |
|
||||
| 04:10 | Project Structure |
|
||||
| 04:54 | Astro Component Anatomy |
|
||||
| 05:23 | Slots & Props |
|
||||
| 06:17 | CSS in Astro |
|
||||
| 07:37 | Astro Config |
|
||||
| 08:38 | VS Code Setup |
|
||||
| 09:55 | Tailwind CSS |
|
||||
| 24:27 | Page Cleanup |
|
||||
| 24:59 | Reusable Components |
|
||||
| 26:17 | Content Collections |
|
||||
| 29:12 | Zod Schema Config |
|
||||
| 30:06 | Blog Listing Page |
|
||||
| 31:33 | Post Components |
|
||||
| 32:49 | Dynamic Routes |
|
||||
| 34:20 | Rendering Content |
|
||||
| 35:08 | Typography Plugin |
|
||||
| 51:01 | Image Optimization |
|
||||
| 52:43 | View Transitions |
|
||||
| 54:10 | MDX Support |
|
||||
| 56:31 | Git + GitHub |
|
||||
| 57:58 | Netlify Deploy |
|
||||
| 58:50 | Vercel Deploy |
|
||||
| 59:50 | SSG vs SSR |
|
||||
| 63:52 | Hybrid Mode |
|
||||
| 65:17 | API Endpoints |
|
||||
| 69:17 | SSR Deployment |
|
||||
| 73:10 | Auth Possibilities |
|
||||
|
||||
33 topics | 0:00 → 1:16:43
|
||||
8
docs/astro-howto/docs/QUESTIONS.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Astro Crash Course — Open Questions
|
||||
|
||||
- Header component code was pasted, not typed — exact Tailwind classes unclear
|
||||
- Content config export syntax cut off in transcript — full object structure unclear
|
||||
- The exact og:image meta tag implementation is vague
|
||||
- "Versome"/"Vercel" pronunciation artifact at 00:47
|
||||
- NVM install vs use — which versions are pre-installed?
|
||||
- MDX component import path conventions not fully shown
|
||||
21
docs/astro-howto/docs/SCREENSHOTS.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Suggested Screenshots
|
||||
|
||||
For visual documentation (extract frames from the video at these moments):
|
||||
|
||||
1. Astro.new website showing starter template options
|
||||
2. Documentation search modal (Cmd+K / Ctrl+K)
|
||||
3. Animated project creation wizard in terminal
|
||||
4. Houston mascot exit animation after project creation
|
||||
5. Starter app at localhost:4321
|
||||
6. VS Code .astro file without extension (plain text)
|
||||
7. VS Code .astro file with Astro extension (syntax highlighted)
|
||||
8. astro.config.mjs showing integrations array with tailwind()
|
||||
9. Blog listing page with grid of post cards
|
||||
10. Individual blog post page with rendered Markdown
|
||||
11. Browser Network tab showing webp images vs original JPEGs
|
||||
12. View Transitions animation between pages
|
||||
13. MDX file with inline components
|
||||
14. Netlify deploy dashboard showing successful build
|
||||
15. Vercel deploy dashboard
|
||||
16. API endpoint returning JSON in browser
|
||||
17. SSR individual post page loading dynamically
|
||||
56
docs/astro-howto/docs/SUMMARY.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Astro Web Framework — Crash Course Summary
|
||||
|
||||
**Source:** Astro Web Framework Crash Course by James Q Quick (freeCodeCamp)
|
||||
**Duration:** 1h 16m | **Transcription:** local faster-whisper (base)
|
||||
|
||||
## What Astro Is
|
||||
|
||||
Astro is an all-in-one web framework for building fast, content-focused websites.
|
||||
It defaults to static site generation (zero JS shipped by default) but can scale
|
||||
up to server-side rendering, API endpoints, and full-stack applications.
|
||||
|
||||
## Course Topics
|
||||
|
||||
| Time | Topic | Description |
|
||||
|------|-------|-------------|
|
||||
| 00:00 | Introduction to Astro | All-in-one framework for content-focused websites |
|
||||
| 00:29 | Course Overview | File-based routing, Markdown/MDX, TypeScript, SSR, view transitions, deployment |
|
||||
| 01:05 | Project Creation | npm create astro@latest scaffolds new project interactively |
|
||||
| 02:01 | Node.js Requirement | Astro 3.0 requires Node.js 18+, NVM recommended |
|
||||
| 03:38 | Opening in VS Code | code -r reuses existing window |
|
||||
| 03:42 | Dev Server | npm run dev starts on port 4321 |
|
||||
| 04:10 | Project Structure | public/ for static assets, src/ for pages and components |
|
||||
| 04:54 | Astro Component Anatomy | Frontmatter (--- fences) for JS + template for HTML |
|
||||
| 05:23 | Slots & Props | Slot for child content, TypeScript interfaces for typed props |
|
||||
| 06:17 | CSS in Astro | Scoped by default, is:global for global styles |
|
||||
| 07:37 | Astro Config | astro.config.mjs for integrations, build settings, SSR |
|
||||
| 08:38 | VS Code Setup | Astro extension (required), Snippets, Houston theme, Tailwind IntelliSense |
|
||||
| 09:55 | Tailwind CSS | npx astro add tailwind auto-installs and configures |
|
||||
| 24:27 | Page Cleanup | Removing default styles, updating page title |
|
||||
| 24:59 | Reusable Components | H1 and Main wrapper components with Tailwind |
|
||||
| 26:17 | Content Collections | Organized Markdown/MDX in src/content/ with type-safe schemas |
|
||||
| 29:12 | Zod Schema Config | src/content/config.ts with defineCollection and Zod |
|
||||
| 30:06 | Blog Listing Page | getCollection('posts') to query and display all posts |
|
||||
| 31:33 | Post Components | PostList grid + Post card with image, title, preview |
|
||||
| 32:49 | Dynamic Routes | pages/blog/[slug].astro with getStaticPaths |
|
||||
| 34:20 | Rendering Content | post.render() → <Content /> for Markdown |
|
||||
| 35:08 | Typography Plugin | @tailwindcss/typography for Markdown styling |
|
||||
| 51:01 | Image Optimization | Astro Image component: auto webp, resize, lazy load |
|
||||
| 52:43 | View Transitions | One component adds smooth page navigation animations |
|
||||
| 54:10 | MDX Support | npx astro add mdx — inline JSX components in Markdown |
|
||||
| 56:31 | Git + GitHub | git init, commit, push to remote |
|
||||
| 57:58 | Netlify Deploy | Import GitHub repo, auto-detect build, deploy static |
|
||||
| 58:50 | Vercel Deploy | Same import flow as Netlify |
|
||||
| 59:50 | SSG vs SSR | Static (build-time HTML) vs Server-rendered (request-time) |
|
||||
| 63:52 | Hybrid Mode | SSR default + export const prerender = true for static pages |
|
||||
| 65:17 | API Endpoints | .ts files in pages/ export GET/POST handlers |
|
||||
| 69:17 | SSR Deployment | npx astro add netlify or vercel adapters |
|
||||
| 73:10 | Auth Possibilities | Cookie-based auth with SSR for full-stack apps |
|
||||
|
||||
**33 topics** spanning 0:00 → 1:16:43
|
||||
|
||||
## Key Takeaway
|
||||
|
||||
Astro lets you start with pure static HTML/Markdown (fast, simple) and
|
||||
progressively add interactivity (SSR, API routes, auth) only when needed.
|
||||
The `npx astro add` command makes integrations nearly one-click.
|
||||
103
docs/astro-howto/run_manifest.json
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
"run_id": "astro-howto-20260531",
|
||||
"created": "2026-05-31T12:21:00+02:00",
|
||||
"source": {
|
||||
"path": "/home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4",
|
||||
"size_bytes": 471704471,
|
||||
"size_human": "449.9 MB",
|
||||
"mtime_unix": 1750287293,
|
||||
"mtime_iso": "2025-06-19T00:54:53+02:00",
|
||||
"codec": "vp9",
|
||||
"resolution": "1920x1080",
|
||||
"fps": 30.0,
|
||||
"duration_s": 4607.6,
|
||||
"duration_human": "1:16:47",
|
||||
"frame_count": 138228,
|
||||
"audio_codec": "aac",
|
||||
"audio_channels": 2,
|
||||
"original_untouched": true
|
||||
},
|
||||
"pipeline": [
|
||||
{
|
||||
"stage": "audio_extraction",
|
||||
"tool": "ffmpeg",
|
||||
"command": "ffmpeg -i video.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 audio.wav",
|
||||
"output": "audio.wav",
|
||||
"size_bytes": 147443712,
|
||||
"size_human": "140.6 MB",
|
||||
"format": "WAV 16kHz mono",
|
||||
"duration_s": 5
|
||||
},
|
||||
{
|
||||
"stage": "transcription",
|
||||
"tool": "faster-whisper",
|
||||
"model": "base",
|
||||
"device": "cpu",
|
||||
"compute_type": "int8",
|
||||
"language": "en",
|
||||
"vad_filter": true,
|
||||
"beam_size": 5,
|
||||
"output": "transcript_local.txt",
|
||||
"lines": 805,
|
||||
"size_bytes": 85547,
|
||||
"audio_duration_s": 4608,
|
||||
"processing_duration_s": 533,
|
||||
"realtime_factor": 8.7
|
||||
},
|
||||
{
|
||||
"stage": "topic_extraction",
|
||||
"tool": "zot_rpc_adapter.py",
|
||||
"model": "glm-5-turbo",
|
||||
"provider": "zai",
|
||||
"chunks": 3,
|
||||
"output": "merged_structure.json",
|
||||
"topics_extracted": 33,
|
||||
"steps_extracted": 18,
|
||||
"commands_extracted": 13
|
||||
},
|
||||
{
|
||||
"stage": "documentation_generation",
|
||||
"outputs": [
|
||||
"docs/SUMMARY.md",
|
||||
"docs/OUTLINE.md",
|
||||
"docs/HOWTO.md",
|
||||
"docs/COMMANDS.md",
|
||||
"docs/QUESTIONS.md",
|
||||
"docs/SCREENSHOTS.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stage": "contact_sheet",
|
||||
"tool": "generate_contact_sheet.py (OpenCV + Pillow)",
|
||||
"script_path": "scripts/generate_contact_sheet.py",
|
||||
"frames_extracted": 77,
|
||||
"frames_skipped": 0,
|
||||
"interval_s": 60,
|
||||
"columns": 5,
|
||||
"thumbnail_width_px": 480,
|
||||
"output": "contact-sheet/contact_sheet.jpg",
|
||||
"dimensions_px": [2408, 4350],
|
||||
"size_bytes": 2932736,
|
||||
"size_human": "2.8 MB",
|
||||
"processing_duration_s": 7.49
|
||||
},
|
||||
{
|
||||
"stage": "screenshot_extraction",
|
||||
"tool": "ffmpeg",
|
||||
"method": "hybrid (-ss after -i for frames 1-8, -ss before -i for 9-17)",
|
||||
"frames_requested": 17,
|
||||
"frames_extracted": 17,
|
||||
"frames_failed": 0,
|
||||
"output": "screenshots/",
|
||||
"total_size_human": "3.2 MB",
|
||||
"index": "screenshots/index.md",
|
||||
"report": "screenshots/report.json"
|
||||
}
|
||||
],
|
||||
"models_used": [
|
||||
{"model": "faster-whisper-base", "purpose": "local transcription", "api_key_used": false},
|
||||
{"model": "glm-5-turbo", "purpose": "topic extraction + script generation", "api_key_used": true, "provider": "zai"}
|
||||
],
|
||||
"api_keys_used_for_media": false,
|
||||
"notes": "All media processing (ffmpeg, faster-whisper, OpenCV, Pillow) ran locally. Only GLM-5-turbo (topic extraction and script generation) used an API key via zot_rpc_adapter."
|
||||
}
|
||||
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 246 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 284 KiB |
26
docs/astro-howto/screenshots/index.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Screenshots Index
|
||||
|
||||
17 frames extracted from Astro Web Framework Crash Course.
|
||||
Source video: `/home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4`
|
||||
|
||||
| # | Timestamp | Description | File |
|
||||
|---|-----------|-------------|------|
|
||||
| 01 | 00:01:05 | Astro.new website showing starter template options | [001_00-01-05_astro.new_website_showing_starter_template_options.jpg](001_00-01-05_astro.new_website_showing_starter_template_options.jpg) |
|
||||
| 02 | 00:01:25 | Documentation search modal opened with Command+K | [002_00-01-25_documentation_search_modal_opened_with_command+k.jpg](002_00-01-25_documentation_search_modal_opened_with_command+k.jpg) |
|
||||
| 03 | 00:02:39 | Animated Astro project creation wizard in terminal | [003_00-02-39_animated_astro_project_creation_wizard_in_terminal.jpg](003_00-02-39_animated_astro_project_creation_wizard_in_terminal.jpg) |
|
||||
| 04 | 00:03:00 | Houston mascot exit animation after project creation | [004_00-03-00_houston_mascot_exit_animation_after_project_creation.jpg](004_00-03-00_houston_mascot_exit_animation_after_project_creation.jpg) |
|
||||
| 05 | 00:04:10 | Starter application at localhost:4321 | [005_00-04-10_starter_application_at_localhost:4321.jpg](005_00-04-10_starter_application_at_localhost:4321.jpg) |
|
||||
| 06 | 00:08:38 | VS Code .astro file without extension showing as plain text | [006_00-08-38_vs_code_.astro_file_without_extension_showing_as_plain_text.jpg](006_00-08-38_vs_code_.astro_file_without_extension_showing_as_plain_text.jpg) |
|
||||
| 07 | 00:09:00 | VS Code .astro file with Astro extension showing syntax highlighting | [007_00-09-00_vs_code_.astro_file_with_astro_extension_showing_syntax_high.jpg](007_00-09-00_vs_code_.astro_file_with_astro_extension_showing_syntax_high.jpg) |
|
||||
| 08 | 00:10:17 | astro.config.mjs showing tailwind() in integrations array | [008_00-10-17_astro.config.mjs_showing_tailwind()_in_integrations_array.jpg](008_00-10-17_astro.config.mjs_showing_tailwind()_in_integrations_array.jpg) |
|
||||
| 09 | 00:31:33 | Blog listing page with grid of post cards | [009_00-31-33_blog_listing_page_with_grid_of_post_cards.jpg](009_00-31-33_blog_listing_page_with_grid_of_post_cards.jpg) |
|
||||
| 10 | 00:34:20 | Individual blog post page with rendered Markdown | [010_00-34-20_individual_blog_post_page_with_rendered_markdown.jpg](010_00-34-20_individual_blog_post_page_with_rendered_markdown.jpg) |
|
||||
| 11 | 00:51:01 | Browser Network tab showing webp images vs original JPEGs | [011_00-51-01_browser_network_tab_showing_webp_images.jpg](011_00-51-01_browser_network_tab_showing_webp_images.jpg) |
|
||||
| 12 | 00:52:43 | View Transitions animation between pages | [012_00-52-43_view_transitions_animation_between_pages.jpg](012_00-52-43_view_transitions_animation_between_pages.jpg) |
|
||||
| 13 | 00:54:10 | MDX file with inline components | [013_00-54-10_mdx_file_with_inline_components.jpg](013_00-54-10_mdx_file_with_inline_components.jpg) |
|
||||
| 14 | 00:57:58 | Netlify deploy dashboard showing successful build | [014_00-57-58_netlify_deploy_dashboard_showing_successful_build.jpg](014_00-57-58_netlify_deploy_dashboard_showing_successful_build.jpg) |
|
||||
| 15 | 00:58:50 | Vercel deploy dashboard | [015_00-58-50_vercel_deploy_dashboard.jpg](015_00-58-50_vercel_deploy_dashboard.jpg) |
|
||||
| 16 | 01:05:17 | API endpoint returning JSON in browser | [016_01-05-17_api_endpoint_returning_json_in_browser.jpg](016_01-05-17_api_endpoint_returning_json_in_browser.jpg) |
|
||||
| 17 | 01:09:17 | SSR individual post page loading dynamically | [017_01-09-17_ssr_individual_post_page_loading_dynamically.jpg](017_01-09-17_ssr_individual_post_page_loading_dynamically.jpg) |
|
||||
|
||||
**Total:** 17 frames, 3.2 MB
|
||||
31
docs/astro-howto/screenshots/report.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"stage": "screenshot_extraction",
|
||||
"tool": "ffmpeg",
|
||||
"source_video": "/home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4",
|
||||
"output_dir": "screenshots/",
|
||||
"frames_requested": 17,
|
||||
"frames_extracted": 17,
|
||||
"frames_failed": 0,
|
||||
"total_size_bytes": 3355443,
|
||||
"total_size_human": "3.2 MB",
|
||||
"seeking_method": "hybrid (frames 1-8: -ss after -i for accuracy; frames 9-17: -ss before -i for speed)",
|
||||
"extraction_timestamps": [
|
||||
{"num": 1, "timestamp": "00:01:05", "file": "001_00-01-05_astro.new_website_showing_starter_template_options.jpg"},
|
||||
{"num": 2, "timestamp": "00:01:25", "file": "002_00-01-25_documentation_search_modal_opened_with_command+k.jpg"},
|
||||
{"num": 3, "timestamp": "00:02:39", "file": "003_00-02-39_animated_astro_project_creation_wizard_in_terminal.jpg"},
|
||||
{"num": 4, "timestamp": "00:03:00", "file": "004_00-03-00_houston_mascot_exit_animation_after_project_creation.jpg"},
|
||||
{"num": 5, "timestamp": "00:04:10", "file": "005_00-04-10_starter_application_at_localhost:4321.jpg"},
|
||||
{"num": 6, "timestamp": "00:08:38", "file": "006_00-08-38_vs_code_.astro_file_without_extension_showing_as_plain_text.jpg"},
|
||||
{"num": 7, "timestamp": "00:09:00", "file": "007_00-09-00_vs_code_.astro_file_with_astro_extension_showing_syntax_high.jpg"},
|
||||
{"num": 8, "timestamp": "00:10:17", "file": "008_00-10-17_astro.config.mjs_showing_tailwind()_in_integrations_array.jpg"},
|
||||
{"num": 9, "timestamp": "00:31:33", "file": "009_00-31-33_blog_listing_page_with_grid_of_post_cards.jpg"},
|
||||
{"num": 10, "timestamp": "00:34:20", "file": "010_00-34-20_individual_blog_post_page_with_rendered_markdown.jpg"},
|
||||
{"num": 11, "timestamp": "00:51:01", "file": "011_00-51-01_browser_network_tab_showing_webp_images.jpg"},
|
||||
{"num": 12, "timestamp": "00:52:43", "file": "012_00-52-43_view_transitions_animation_between_pages.jpg"},
|
||||
{"num": 13, "timestamp": "00:54:10", "file": "013_00-54-10_mdx_file_with_inline_components.jpg"},
|
||||
{"num": 14, "timestamp": "00:57:58", "file": "014_00-57-58_netlify_deploy_dashboard_showing_successful_build.jpg"},
|
||||
{"num": 15, "timestamp": "00:58:50", "file": "015_00-58-50_vercel_deploy_dashboard.jpg"},
|
||||
{"num": 16, "timestamp": "01:05:17", "file": "016_01-05-17_api_endpoint_returning_json_in_browser.jpg"},
|
||||
{"num": 17, "timestamp": "01:09:17", "file": "017_01-09-17_ssr_individual_post_page_loading_dynamically.jpg"}
|
||||
]
|
||||
}
|
||||
63
docs/astro-howto/scripts/extract_screenshots.sh
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
#!/bin/bash
|
||||
# Extract targeted screenshots from Astro video
|
||||
# Generated by Hermes — read-only, preserves original
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VIDEO="/home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4"
|
||||
OUT="/home/samob/ai/astro-howto/screenshots"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
echo "[1/17] 00:01:05 — Astro.new website showing starter template options"
|
||||
ffmpeg -y -i "$VIDEO" -ss 65 -frames:v 1 -q:v 2 "$OUT/001_00-01-05_astro.new_website_showing_starter_template_options.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[2/17] 00:01:25 — Documentation search modal opened with Command+K"
|
||||
ffmpeg -y -i "$VIDEO" -ss 85 -frames:v 1 -q:v 2 "$OUT/002_00-01-25_documentation_search_modal_opened_with_command+k.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[3/17] 00:02:39 — Animated Astro project creation wizard in terminal"
|
||||
ffmpeg -y -i "$VIDEO" -ss 159 -frames:v 1 -q:v 2 "$OUT/003_00-02-39_animated_astro_project_creation_wizard_in_terminal.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[4/17] 00:03:00 — Houston mascot exit animation after project creation"
|
||||
ffmpeg -y -i "$VIDEO" -ss 180 -frames:v 1 -q:v 2 "$OUT/004_00-03-00_houston_mascot_exit_animation_after_project_creation.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[5/17] 00:04:10 — Starter application at localhost:4321"
|
||||
ffmpeg -y -i "$VIDEO" -ss 250 -frames:v 1 -q:v 2 "$OUT/005_00-04-10_starter_application_at_localhost:4321.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[6/17] 00:08:38 — VS Code .astro file without extension showing as plain text"
|
||||
ffmpeg -y -i "$VIDEO" -ss 518 -frames:v 1 -q:v 2 "$OUT/006_00-08-38_vs_code_.astro_file_without_extension_showing_as_plain_text.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[7/17] 00:09:00 — VS Code .astro file with Astro extension showing syntax highlighting"
|
||||
ffmpeg -y -i "$VIDEO" -ss 540 -frames:v 1 -q:v 2 "$OUT/007_00-09-00_vs_code_.astro_file_with_astro_extension_showing_syntax_high.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[8/17] 00:10:17 — astro.config.mjs showing tailwind() in integrations array"
|
||||
ffmpeg -y -i "$VIDEO" -ss 617 -frames:v 1 -q:v 2 "$OUT/008_00-10-17_astro.config.mjs_showing_tailwind()_in_integrations_array.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[9/17] 00:31:33 — Blog listing page with grid of post cards"
|
||||
ffmpeg -y -i "$VIDEO" -ss 1893 -frames:v 1 -q:v 2 "$OUT/009_00-31-33_blog_listing_page_with_grid_of_post_cards.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[10/17] 00:34:20 — Individual blog post page with rendered Markdown"
|
||||
ffmpeg -y -i "$VIDEO" -ss 2060 -frames:v 1 -q:v 2 "$OUT/010_00-34-20_individual_blog_post_page_with_rendered_markdown.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[11/17] 00:51:01 — Browser Network tab showing webp images vs original JPEGs"
|
||||
ffmpeg -y -i "$VIDEO" -ss 3061 -frames:v 1 -q:v 2 "$OUT/011_00-51-01_browser_network_tab_showing_webp_images_vs_original_jpegs.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[12/17] 00:52:43 — View Transitions animation between pages"
|
||||
ffmpeg -y -i "$VIDEO" -ss 3163 -frames:v 1 -q:v 2 "$OUT/012_00-52-43_view_transitions_animation_between_pages.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[13/17] 00:54:10 — MDX file with inline components"
|
||||
ffmpeg -y -i "$VIDEO" -ss 3250 -frames:v 1 -q:v 2 "$OUT/013_00-54-10_mdx_file_with_inline_components.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[14/17] 00:57:58 — Netlify deploy dashboard showing successful build"
|
||||
ffmpeg -y -i "$VIDEO" -ss 3478 -frames:v 1 -q:v 2 "$OUT/014_00-57-58_netlify_deploy_dashboard_showing_successful_build.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[15/17] 00:58:50 — Vercel deploy dashboard"
|
||||
ffmpeg -y -i "$VIDEO" -ss 3530 -frames:v 1 -q:v 2 "$OUT/015_00-58-50_vercel_deploy_dashboard.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[16/17] 01:05:17 — API endpoint returning JSON in browser"
|
||||
ffmpeg -y -i "$VIDEO" -ss 3917 -frames:v 1 -q:v 2 "$OUT/016_01-05-17_api_endpoint_returning_json_in_browser.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
echo "[17/17] 01:09:17 — SSR individual post page loading dynamically"
|
||||
ffmpeg -y -i "$VIDEO" -ss 4157 -frames:v 1 -q:v 2 "$OUT/017_01-09-17_ssr_individual_post_page_loading_dynamically.jpg" 2>&1 | tail -1
|
||||
echo ""
|
||||
|
||||
echo "Done. All 17 screenshots extracted."
|
||||
26
docs/astro-howto/scripts/extract_screenshots_fast.sh
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
# Fast extract remaining screenshots (9-17)
|
||||
set -euo pipefail
|
||||
VIDEO="/home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4"
|
||||
OUT="/home/samob/ai/astro-howto/screenshots"
|
||||
|
||||
# 9-17
|
||||
ffmpeg -y -ss 1893 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/009_00-31-33_blog_listing_page_with_grid_of_post_cards.jpg" 2>&1 | tail -1
|
||||
echo "[9/17] done"
|
||||
ffmpeg -y -ss 2060 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/010_00-34-20_individual_blog_post_page_with_rendered_markdown.jpg" 2>&1 | tail -1
|
||||
echo "[10/17] done"
|
||||
ffmpeg -y -ss 3061 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/011_00-51-01_browser_network_tab_showing_webp_images.jpg" 2>&1 | tail -1
|
||||
echo "[11/17] done"
|
||||
ffmpeg -y -ss 3163 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/012_00-52-43_view_transitions_animation_between_pages.jpg" 2>&1 | tail -1
|
||||
echo "[12/17] done"
|
||||
ffmpeg -y -ss 3250 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/013_00-54-10_mdx_file_with_inline_components.jpg" 2>&1 | tail -1
|
||||
echo "[13/17] done"
|
||||
ffmpeg -y -ss 3478 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/014_00-57-58_netlify_deploy_dashboard_showing_successful_build.jpg" 2>&1 | tail -1
|
||||
echo "[14/17] done"
|
||||
ffmpeg -y -ss 3530 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/015_00-58-50_vercel_deploy_dashboard.jpg" 2>&1 | tail -1
|
||||
echo "[15/17] done"
|
||||
ffmpeg -y -ss 3917 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/016_01-05-17_api_endpoint_returning_json_in_browser.jpg" 2>&1 | tail -1
|
||||
echo "[16/17] done"
|
||||
ffmpeg -y -ss 4157 -i "$VIDEO" -frames:v 1 -q:v 2 "$OUT/017_01-09-17_ssr_individual_post_page_loading_dynamically.jpg" 2>&1 | tail -1
|
||||
echo "[17/17] done"
|
||||
echo "All remaining screenshots extracted."
|
||||
143
docs/astro-howto/scripts/generate_contact_sheet.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import math
|
||||
import argparse
|
||||
import threading
|
||||
import cv2
|
||||
from PIL import Image
|
||||
|
||||
def read_with_timeout(cap, timeout=5.0):
|
||||
result = [None, None]
|
||||
exception = [None]
|
||||
def target():
|
||||
try:
|
||||
result[0], result[1] = cap.read()
|
||||
except Exception as e:
|
||||
exception[0] = e
|
||||
result[0] = False
|
||||
thread = threading.Thread(target=target)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
thread.join(timeout=timeout)
|
||||
if thread.is_alive():
|
||||
return False, None
|
||||
if exception[0]:
|
||||
return False, None
|
||||
return result[0], result[1]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate a video contact sheet.")
|
||||
parser.add_argument("--save-frames", action="store_true", help="Save individual frames")
|
||||
args = parser.parse_args()
|
||||
|
||||
video_path = "/home/samob/Videos/Astro/Astro Web Framework Crash Course [e-hTm5VmofI].mp4"
|
||||
output_dir = "/home/samob/ai/astro-howto/contact-sheet/"
|
||||
frames_dir = os.path.join(output_dir, "contact_sheet_frames")
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
if args.save_frames:
|
||||
os.makedirs(frames_dir, exist_ok=True)
|
||||
|
||||
if not os.path.exists(video_path):
|
||||
print(f"Error: Source video not found at {video_path}")
|
||||
sys.exit(1)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
if not cap.isOpened():
|
||||
print("Error: Could not open video file.")
|
||||
sys.exit(1)
|
||||
|
||||
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
interval_frames = int(60 * fps)
|
||||
|
||||
targets = list(range(0, total_frames, interval_frames))
|
||||
|
||||
thumbnails = []
|
||||
skipped = 0
|
||||
count = 0
|
||||
|
||||
for target_pos in targets:
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, target_pos)
|
||||
ret, frame = read_with_timeout(cap, timeout=5.0)
|
||||
|
||||
if not ret or frame is None:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
h, w = frame_rgb.shape[:2]
|
||||
new_w = 480
|
||||
new_h = int((new_w / w) * h)
|
||||
resized = cv2.resize(frame_rgb, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
||||
|
||||
pil_img = Image.fromarray(resized)
|
||||
|
||||
if args.save_frames:
|
||||
frame_filename = f"frame_{count:03d}.jpg"
|
||||
pil_img.save(os.path.join(frames_dir, frame_filename), quality=90)
|
||||
|
||||
thumbnails.append(pil_img)
|
||||
count += 1
|
||||
except Exception:
|
||||
skipped += 1
|
||||
|
||||
cap.release()
|
||||
|
||||
if not thumbnails:
|
||||
print("Error: No frames could be extracted.")
|
||||
sys.exit(1)
|
||||
|
||||
cols = 5
|
||||
thumb_w, thumb_h = thumbnails[0].size
|
||||
padding = 2
|
||||
rows = math.ceil(len(thumbnails) / cols)
|
||||
|
||||
sheet_w = cols * thumb_w + (cols - 1) * padding
|
||||
sheet_h = rows * thumb_h + (rows - 1) * padding
|
||||
|
||||
sheet = Image.new('RGB', (sheet_w, sheet_h), (255, 255, 255))
|
||||
|
||||
for i, img in enumerate(thumbnails):
|
||||
r = i // cols
|
||||
c = i % cols
|
||||
x = c * (thumb_w + padding)
|
||||
y = r * (thumb_h + padding)
|
||||
sheet.paste(img, (x, y))
|
||||
|
||||
output_path = os.path.join(output_dir, "contact_sheet.jpg")
|
||||
sheet.save(output_path, quality=95)
|
||||
|
||||
extraction_time = time.time() - start_time
|
||||
|
||||
report = {
|
||||
"frame_count": count,
|
||||
"path": output_path,
|
||||
"dimensions_px": [sheet_w, sheet_h],
|
||||
"skipped_frames": skipped,
|
||||
"extraction_time_s": round(extraction_time, 2)
|
||||
}
|
||||
|
||||
report_path = os.path.join(output_dir, "report.json")
|
||||
with open(report_path, 'w') as f:
|
||||
json.dump(report, f, indent=4)
|
||||
|
||||
if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
|
||||
print("Error: Output validation failed - file missing or empty.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Frames extracted: {count}")
|
||||
print(f"Skipped: {skipped}")
|
||||
print(f"Output path: {output_path}")
|
||||
print(f"Dimensions: {sheet_w}x{sheet_h}px")
|
||||
print(f"Time: {report['extraction_time_s']}s")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
45
docs/astro-howto/scripts/transcribe_local.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Local audio transcription using faster-whisper.
|
||||
Usage: python3 transcribe_local.py <audio.wav> [model]
|
||||
|
||||
Models: tiny, base (default), small, medium, large-v3
|
||||
Smaller = faster, larger = more accurate.
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
def main():
|
||||
audio_path = sys.argv[1] if len(sys.argv) > 1 else "audio.wav"
|
||||
model_size = sys.argv[2] if len(sys.argv) > 2 else "base"
|
||||
output_path = audio_path.rsplit(".", 1)[0] + "_transcript.txt"
|
||||
|
||||
print(f"Loading {model_size} model...")
|
||||
model = WhisperModel(model_size, device='cpu', compute_type='int8', num_workers=8)
|
||||
|
||||
print(f"Transcribing {audio_path}...")
|
||||
start = time.time()
|
||||
segments, info = model.transcribe(
|
||||
audio_path,
|
||||
language='en',
|
||||
beam_size=5,
|
||||
vad_filter=True,
|
||||
vad_parameters=dict(min_silence_duration_ms=500),
|
||||
)
|
||||
|
||||
with open(output_path, 'w') as f:
|
||||
for seg in segments:
|
||||
ts = (
|
||||
f'[{seg.start:.0f}s]'
|
||||
if seg.start < 3600
|
||||
else f'[{int(seg.start // 3600)}:{int(seg.start % 3600 // 60):02d}:{int(seg.start % 60):02d}]'
|
||||
)
|
||||
f.write(f'{ts} {seg.text.strip()}\n')
|
||||
|
||||
elapsed = time.time() - start
|
||||
print(f"Done. {info.duration:.0f}s audio in {elapsed:.0f}s ({info.duration / elapsed:.1f}x realtime)")
|
||||
print(f"Output: {output_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
805
docs/astro-howto/transcript_local.txt
Normal file
|
|
@ -0,0 +1,805 @@
|
|||
[0s] Astro is an all-in-one web framework for building fast content-focused websites,
|
||||
[6s] like landing pages, blogs, technical documentation, and more.
|
||||
[10s] In this course, James QQuick will teach you about this increasingly popular framework.
|
||||
[16s] James is a popular instructor and keynote conference speaker.
|
||||
[20s] He's the perfect person to teach you about Astro.
|
||||
[24s] Let's learn all about Astro, one of the most exciting and up-and-coming JavaScript frameworks.
|
||||
[29s] My name is James QQuick, and I absolutely love Astro.
|
||||
[32s] So in this crash course, we're going to cover some of the core concepts of Astro along the way.
|
||||
[36s] We'll talk about file-based routing, creating and managing markdown and MDX content
|
||||
[41s] with content collections and TypeScript.
|
||||
[44s] We'll talk about dynamic routes, and we'll even, at the end of this video,
|
||||
[47s] get into deploying this to Netlify and Versome.
|
||||
[50s] We'll also talk about the server side capabilities of Astro going from a statically generated site
|
||||
[55s] to a server side rendered site and show you how you can add server endpoints to your
|
||||
[59s] Astro applications as well.
|
||||
[61s] Lastly, we'll talk about a few neat features of Astro along the way,
|
||||
[64s] like view transitions, which allows you to add beautiful transitions between your pages
|
||||
[69s] and your Astro application with just one line of code.
|
||||
[71s] Now, if you enjoy what we cover in this crash course and you want to learn more,
|
||||
[75s] you can check out my full course at astrocourse.dev to learn all the ins and outs of Astro 3.0.
|
||||
[82s] That said, let's go ahead and get started.
|
||||
[85s] Let's start on the Astro documentation page where we can see the getting started
|
||||
[88s] instructions for creating a brand new project with Astro version 3, which is the most recent version
|
||||
[93s] of Astro. Now, on this documentation page, they kind of give you an overview, some of the things
|
||||
[98s] that we've talked about, some key features, etc. And then if you scroll down, they'll give you
|
||||
[102s] a couple of ways to get started with Astro. One cool way to do that is with the Astro.new website,
|
||||
[107s] and what this is, is a collection of different Astro projects that you can open up that have already
|
||||
[112s] been created and kind of get started working with them right inside of stacklets.
|
||||
[116s] Now, in this case, we're going to create our own project from scratch. So, we're going to do this
|
||||
[120s] inside of our own VS code window and terminal to be able to create the project and then go and work
|
||||
[125s] and build out the tutorial or the blog that we're going to build. Now, one thing I do want to show
|
||||
[130s] and we'll kind of reference this throughout this crash course is the ability to search for anything
|
||||
[134s] that we might want in this course on the Astro documentation page. So, once you get to the Astro
|
||||
[140s] docs, you can come up to the top left and you can click to open the search window and you can search for
|
||||
[145s] server, anything, server, side, rendering, for example. And then, click on one of these and go
|
||||
[151s] straight to those pages. The other thing I want to show you that's pretty neat is on this page,
|
||||
[155s] you can hold Command K on Mac or Control K on Windows to be able to pull that up as well. So,
|
||||
[161s] you don't have to go and click that yourself. You can just open that with the shortcut window.
|
||||
[165s] That said, what we're going to do is copy this command to be able to create a brand new Astro project
|
||||
[170s] and then run this in our terminal so that we can get started. So, I'm going to scroll over to my
|
||||
[174s] VS code window and this is an open MTVS code window and I'm going to first switch over to the directory
|
||||
[180s] that I want to create the project. So, I'm going to switch into code and then my demos directory.
|
||||
[186s] And then from here, I'm going to paste in that command from the Astro docs to create the new project.
|
||||
[191s] So, once I press enter, this will ask me a few different things. First of all, are we okay installing
|
||||
[195s] the package that's needed to generate our project? And this is actually a good error to have here.
|
||||
[201s] So, with the latest version of Astro, you're required to have a version 18 or higher of Node.js to be
|
||||
[207s] able to run the installer. So, I wanted to leave this in as a reminder that you'll need to be working
|
||||
[212s] with a version 18 or higher. Now, for me personally, I use NVM to manage my different versions of Node
|
||||
[220s] to handle this. This is the easiest way that I found to handle working with different versions of Node.
|
||||
[225s] So, I can use my NVM command and then NVM use and then type 18. And this will let me use a version
|
||||
[231s] of Node that is 18. Now, I can also use NVM to install a version of Node like 20, for example.
|
||||
[239s] And that will go in install a different version of Node that I could use at any time. But in this case,
|
||||
[243s] since I'm now using version 18, I can scroll back through my commands and run that same create command
|
||||
[249s] inside of my terminal. Now, you see, we get the really awesome animated experience where Astro is
|
||||
[255s] going to walk us through creating this new project. So, in this case, we need to give it a folder
|
||||
[260s] that we're going to create the project in. I'm going to call this FCC Astro Crash Course. So,
|
||||
[267s] you'll just do .slash and then whatever the folder is of whatever the name is of the folder that you
|
||||
[272s] want it to create in. In this case, we're going to start with sample files. You could also choose a
|
||||
[277s] blog template where they'll have a lot of the stuff done that we will be building from scratch. You
|
||||
[282s] could also additionally choose an empty project to just start completely from scratch.
|
||||
[286s] In this case, we'll accept the sample files. And what it's doing now is it's going to start to copy
|
||||
[290s] over the sample files, which is done. And then it's going to ask whether or not we want it to install
|
||||
[294s] the dependencies and we can press Enter on Yes and let it go and run and install all of these dependencies.
|
||||
[302s] So, we'll let that run for a second and come back when it's finished. All right, so those dependencies
|
||||
[306s] have been installed. And the next question is, do we plan to write TypeScript, which in this case,
|
||||
[310s] we do. So, we'll click Yes. And then we'll choose the strict or recommended version of how strict
|
||||
[315s] with TypeScript we should be. Lastly, the question is, do we want this to initialize a new Git repository?
|
||||
[321s] In this case, we'll say yes, because we're going to use this repository to deploy this later on to
|
||||
[326s] Netlify and Versel. So, we'll click Yes here. And then we get our out animation from Houston, which
|
||||
[333s] is the Astro mascot. We'll come back to Houston in a little bit. And the next thing I want to do is
|
||||
[338s] open this project inside of VS Code. So, I can use the command Code-R, which means to reuse
|
||||
[344s] the window that I'm currently in. And then I'm going to choose my FCC Astro Crash Course window.
|
||||
[350s] Notice it pops up here with this intelligence. If you're curious where this little window is coming
|
||||
[354s] from, this is coming from an extension called Fig.io, which is really great to kind of supercharge
|
||||
[360s] your powers inside of your terminal. So, I'm going to press Enter and then we'll open this up inside
|
||||
[365s] of that same window in VS Code. And now I'm going to go to the bottom and open my terminal again.
|
||||
[370s] And inside of here, I'm going to run NPM Run Dev. And this should now start our Astro project.
|
||||
[376s] And it will run it at port 4321. They chose this port because it is kind of like 4321 blast off,
|
||||
[383s] which is kind of neat. So, I'm going to open a new tab in my browser and open up my browser to
|
||||
[388s] localhost 4321. And you see, this is the starter application that we get with Astro, or we have
|
||||
[395s] a welcome to Astro. We have a little code challenge of how to make an update to this, which is right here.
|
||||
[400s] Then we have links to documentation, integration, themes, community, etc. So, we have our beginner
|
||||
[406s] Astro application created. Let's go and walk through the code and talk a little bit about what all
|
||||
[410s] is there. So, first off, we have our public directory. This is where we would store any public assets
|
||||
[416s] like images or other things that we want to be publicly available from our site. Now,
|
||||
[422s] these are going to be directly available after the end of the URL. So, what this means is,
|
||||
[426s] since we have favicon.svg, we could come to the end of our URL and type in favicon.svg.
|
||||
[434s] And now that'll take us to that file, which is not going to show a whole lot because it's an SVG.
|
||||
[438s] And we're not going to have good visibility here. But we do have the access to be able to access
|
||||
[442s] that directly. So, anything that you put inside of the public directory will just be shipped with
|
||||
[447s] your built version of your site and included and would be available after the slash in your URL.
|
||||
[454s] After that, we have our source directory. This is where all of our code is really going to live.
|
||||
[458s] And what Astro really depends on is this page-based routing where we have different files under
|
||||
[464s] the pages directory are going to represent, as you might have guessed it, different pages in your
|
||||
[468s] application. So, let's just open the index.astro file and take a look. The first thing we'll
|
||||
[475s] notice is that it is a .astro file. It's not a .js. It's not a .ts. This is .astro. And this obviously
|
||||
[482s] is going to signify to the developer to yourself and to your editor. We'll talk about this in a minute
|
||||
[488s] that this is an astro component. Now, astro components are made up of two different sections. There is the
|
||||
[494s] kind of JavaScript section which goes in these three-blocks. We can call these front-matter. We'll
|
||||
[500s] talk more about front-matter inside of Markdown files. But inside of these three-blocks, we can add
|
||||
[508s] any sort of JavaScript that we want, including importing other components. So, you can see we import
|
||||
[513s] our layout and our card. And in this case, we can find both of these in their appropriate
|
||||
[518s] directories. Under layout is the first one. If we scroll down, we see a reference to slot.
|
||||
[523s] Now, slot is where we're going to take whatever information is in between this component when we use it,
|
||||
[528s] the layout component, and then inject that right inside of the component that we have defined here.
|
||||
[534s] So, what this looks like, if we come down to our index.astro, since we wrap this entire page,
|
||||
[539s] which has a lot of code in it, since we wrap this entire page with this layout component, all of
|
||||
[545s] the stuff in between the layout tags, which is here. All of that is considered the slot. And that
|
||||
[551s] will be rendered inside of this layout component right here. So, inside of the body. Now, inside of the
|
||||
[557s] layout, you'll also see a few other things. You'll see that we can define TypeScript interfaces for
|
||||
[563s] our props. After we define those, we can then destructure those properties and then use them inside
|
||||
[568s] of our application, just like we're using the title inside of the title tag inside of the head
|
||||
[574s] for our application. Now, notice we can use these JavaScript variables by putting them in between
|
||||
[581s] the two brackets. So, inside of these two brackets, we're able to basically write JavaScript here,
|
||||
[587s] which enables us to use variables that we've defined up above. So, in this layout file, you'll also see
|
||||
[593s] the other components that make up a basic HTML file. You'll see the doc type defined at the top.
|
||||
[599s] You'll see the HTML tag. You'll see head and we'll see a few different of our meta tags here,
|
||||
[606s] like description, viewport, etc. We'll also see a reference to our title. And then if we scroll down,
|
||||
[612s] we'll see a lot of CSS in here. Now, Ashter has a few different ways that you can write CSS. One of the
|
||||
[617s] ways that you can do that is just by adding a style tag right inside of your Ashtero components.
|
||||
[623s] Now, this may or may not make you excited. This is something that gets debated about whether or not your
|
||||
[628s] style should be co-located with your actual markdown and with your JavaScript. But in Ashtero,
|
||||
[632s] you have the ability to write all three together. So, your JavaScript, your markup, or your HTML,
|
||||
[639s] and then your styles in here as well. Now, styles typically in here are scoped to a given component.
|
||||
[646s] So, you can see here that we have an is global tag that's associated with this style,
|
||||
[651s] which means all of these styles are going to be applied to every single page on our application,
|
||||
[656s] versus if we go to our index.astero component and scroll down, there's going to be styles here.
|
||||
[662s] And these styles are not global. They're only referencing material that's inside of the
|
||||
[668s] component that it's in, which in this case is a page component, which is the index.astero. Now,
|
||||
[673s] we can see another good example of this with the actual card component. You can see we define our
|
||||
[678s] props here. We destructure those props. We have markup. We reference those properties inside of our
|
||||
[684s] markup. And then we also have style tags down here as well. Now, again, these styles are only
|
||||
[689s] applying to things that are inside of this card component and won't be applied anywhere else.
|
||||
[695s] So, an example of this is if I were to select the main tag and did a background color of red,
|
||||
[704s] this actually won't appear to have any difference or make any difference on our application.
|
||||
[709s] And that's because there's no main tag inside of this card component and these styles are only
|
||||
[714s] applied to that. However, if I went to the layout and I now chose to select the main tag and did a
|
||||
[721s] background of red, now we'll see that this red color is going to come into play because these styles
|
||||
[729s] are global and are going to be applied anywhere there is a main tag. So, really important to remember
|
||||
[735s] that the styles inside of astro by default are scoped to that component and won't be interfering
|
||||
[741s] with other styles that you have in other components. Now, in this case and this crash course,
|
||||
[745s] we're going to use tailwind CSS to style our application. So, we're actually not going to worry about
|
||||
[750s] all of these styles that are defined inside of here. We'll come back and clean these out in a minute
|
||||
[754s] and kind of reset this with some beginner styles for us to work with. But if you were building an
|
||||
[759s] astro project yourself, you do have a few different ways that you could choose to do CSS. In this case,
|
||||
[764s] we are just going to use tailwind CSS, which has become incredibly popular. So, that's the majority
|
||||
[771s] of the basics of the layout for your code inside of the source directory. There's a few other files
|
||||
[777s] that get ignore file, which is pretty standard. There's also the astro.config file. And this is
|
||||
[782s] really important because this is where we can add integrations and astro. We can also define different
|
||||
[788s] things about our project like how this project is going to be built and where it's going to be hosted.
|
||||
[794s] So, by default, astro is a statically generated site. We'll talk more and more about this.
|
||||
[799s] We don't have to configure anything for that to be supported. But if we wanted to convert this to be
|
||||
[803s] a server side rendered site, we could configure this in here and then configure where and how we want
|
||||
[808s] to deploy this. At the end of this video, we'll talk about deploying this to both Netlify and Versel.
|
||||
[813s] But in this case, we don't have anything to change yet in the astro config, although we will come back
|
||||
[818s] to this shortly. Next up, we have our package.json with a few commands on how to run the project. We have
|
||||
[823s] the readme and then we have a TS config, which just extends the TypeScript config that comes from
|
||||
[829s] astro. So, this is going to give us all the basic rules for working with TypeScript inside of our
|
||||
[833s] astro project. You could go and customize that in any way that you want to, but in this case, we don't
|
||||
[838s] need to. Now, I want to take a few minutes to talk about setting up your VS Code instance to work with
|
||||
[844s] astro in the best way. Now, the most important thing you'll need to install is the actual astro
|
||||
[850s] extension, which comes from astro themselves. Now, what this does is it allows these astro components,
|
||||
[856s] these astro files to be recognized as astro files so that we get appropriate intelligence, coloring,
|
||||
[862s] etc. So, notice down at the bottom here that VS Code is recognizing this as an astro file and then
|
||||
[868s] based on that and based on this extension knows what to do and how to color this. Now, if we were to
|
||||
[873s] disable this just to show you what this looks like and we restart this, all of our code highlighting
|
||||
[880s] our syntax, our intelligence, etc. goes away inside of these astro components and VS Code considers
|
||||
[886s] this to be a plain text file, which is obviously not what we want. So, you'll want to make sure to install
|
||||
[892s] the astro extension to get all of the benefits that come along with it. It's by far the best way to work
|
||||
[898s] with astro. So, I'm going to enable that and notice all of that comes back. Now, another one that I
|
||||
[902s] have installed is an astro snippets extension. There's lots that you can do with astro in terms of
|
||||
[907s] different types of files, different things you might want to do. This is a great set of snippets that
|
||||
[912s] you could start with to kind of help generate components quicker and easier for you as you're going along.
|
||||
[917s] Now, there's one more extension from the astro team, which is the Houston extension. Now,
|
||||
[922s] Houston is the actual mascot for astro and they've built a lot of fun things around this. So, with the
|
||||
[927s] Houston extension, you get the astro VS Code theme mimicking the colors from Houston, which is
|
||||
[933s] pretty neat. I like this a lot. In addition to that inside of the file explorer, you get a little
|
||||
[938s] Houston tab and you get kind of an animated Houston icon that shows you whether or not your application
|
||||
[944s] is running well or not based on it being happy or sad. So, it's a nice little touch to kind of feel
|
||||
[949s] like you're inside of the astro community. So, you can install this and kind of have some fun with
|
||||
[954s] that if you're interested. Now, me personally, I'm using my personal James Q quick themes. So,
|
||||
[959s] if you're interested in having your colors look exactly like mine, you can search James Q quick to
|
||||
[963s] get set up there. The last thing I want to show you is the tailwind CSS IntelliSense extension. This is
|
||||
[970s] one that I use all the time when working with tailwind so that it helps me kind of auto-complete or
|
||||
[975s] remember what all the different styles are that I'm trying to work with. So, you'll see this more as we
|
||||
[980s] work work through this and start writing some code. Now, with all of that set up, let's go back
|
||||
[985s] to the astro documentation and see how to install tailwind. So, let's just search in the documentation
|
||||
[990s] for tailwind and we'll be taken to the astro JS tailwind extension or integration that we can add
|
||||
[996s] which is one of the really cool things about astro is that it comes with integrations which makes
|
||||
[1001s] it really easy to add support for other UI frameworks, for example, to be able to deploy SSR to different
|
||||
[1007s] places and a bunch of other really neat things. So, let's scroll down. We can kind of skip the
|
||||
[1011s] wide tailwind and let's come down to the npx astro add tailwind command which allows us to add tailwind
|
||||
[1017s] in one command and be able to work with it right after that. So, let's stop our running application.
|
||||
[1022s] Let's paste in our npx astro add tailwind command and this will kind of walk us through what it's
|
||||
[1028s] going to do to make sure we're okay with it doing all of these things. So, do we want to allow it to
|
||||
[1033s] install the tailwind CSS extension and the astro JS tailwind package. Yes, absolutely. In this case,
|
||||
[1039s] it says it's going to generate a tailwind config file which we absolutely want. So, we'll say yes
|
||||
[1044s] and then lastly, it says it's going to update the astro config file to be able to support tailwind.
|
||||
[1049s] So, we'll say yes to that as well. Now, just to confirm what this did, let's search for the astro
|
||||
[1053s] config file. Let's open this up and what it did is it added an integration section here and an inside
|
||||
[1059s] of that array it added a call to the tailwind function that gets imported from the astro JS tailwind
|
||||
[1066s] package. So, this in theory is how we would manually install integrations into our applications.
|
||||
[1072s] But in this case, astro gives us this command, the astro add command to be able to do all this
|
||||
[1077s] a force which is really, really nice. So, let's go into our layout file and let's get rid of all
|
||||
[1083s] of the styles that are defined in here because we're going to use tailwind for our styles and not use
|
||||
[1087s] the built-in styles that come with the application. Now, just to make sure let's go ahead and run
|
||||
[1092s] this to have this running, we can make a few changes inside of this layout file to start to get the
|
||||
[1099s] base of our application going. Now, the first thing to notice is that we're only defining one
|
||||
[1104s] property that could be passed in as props which is the title. Now, we could additionally add extra ones
|
||||
[1110s] like the description. We can have this be optional. So, we'll have that be defined as a string. So,
|
||||
[1115s] the optional question mark or the question mark denotes this as optional. And we can also define an
|
||||
[1121s] image in here as well. So, now that we get all three of those, we can de-structure them so that we can
|
||||
[1127s] be able to use these as well. All right, so we have these three properties, but we're not using all
|
||||
[1132s] of them just yet. We're only referencing the title and not the description or the image yet.
|
||||
[1139s] So, inside of the content for the description, we could reset this
|
||||
[1145s] to be description. And we could also reference our image by using it for the OG image type.
|
||||
[1152s] Now, we're not going to get all the way into all of the different OG tags that we could use.
|
||||
[1156s] Let's start with a meta tag with a property of OG image. And then we'll say the content
|
||||
[1165s] is going to be that image property that we pass in. So, we'll put this in here as image.
|
||||
[1171s] Now, one thing you might be wondering is what if the description and the image are not pass in here,
|
||||
[1177s] we should probably have a default property that we can use. So, in this case, for the description,
|
||||
[1182s] we can set the default right in line in here by doing equals and then assigning this to a string.
|
||||
[1187s] Now, what we're building is an application called Rhythm Nation. And this is a community
|
||||
[1193s] of music producers and enthusiasts. And then we want to give a default value to the image as well.
|
||||
[1202s] Now, we'll come back to this in the images section. But I'm going to set this to a default of slash
|
||||
[1208s] images slash band.jpeg. Now, remember, we talked about the public directory. What this is referring to
|
||||
[1215s] would be a file band.jpeg inside of an images directory inside of public. We'll come back to that
|
||||
[1222s] in a little bit. For now, we're just kind of setting these by default. Now, just to show that these
|
||||
[1226s] are coming up, we can come back to our running application, which now looks a little bit different
|
||||
[1232s] because we got rid of those styles. And if we look inside of the head, we should see that we now see
|
||||
[1237s] our description here. We also see our OG image, which if we try to access will not be available yet.
|
||||
[1242s] Then we still have our same title, which is great. So, all those things are working well. There's a lot
|
||||
[1247s] more that you could dive into with OG tags for helping your website show up on social media.
|
||||
[1254s] Post, for example, or embeds and Slack or discord. But that's the conversation for another day.
|
||||
[1259s] Just know that you have complete control to add all of those inside of here. Now, inside of the layout,
|
||||
[1264s] what I want to do is add some tailwind classes in here. And what we can start with is a men height
|
||||
[1270s] of screen. So, before we save that, let's go and look at the body tag in here.
|
||||
[1277s] If we see this body tag is not taking up the entire height. So, we can start to style that a little bit
|
||||
[1283s] with men h of screen. And now we should see that this body is now taking up the entire height, which
|
||||
[1288s] also is confirming that our tailwind classes are working. Now, from here, I want to add a header
|
||||
[1295s] component that can show the basics of our application. So, in this case, I'm going to copy a little bit of
|
||||
[1300s] code, but inside of components, I'm going to create the header dot astro component. And I'm going to
|
||||
[1306s] paste in some starter code for us to work with. Now, we'll walk through the code that's here. So,
|
||||
[1311s] we define our header. We give some tailwind CSS classes. Again, this is not a crash course on tailwind
|
||||
[1316s] specifically, but we have some classes for our header. We then have an image icon that we can have
|
||||
[1322s] to show in the top left. We'll come back to that in a second. Then we have a few links to the
|
||||
[1326s] different pages on this application, like the homepage, the about and the blog. So, from here,
|
||||
[1332s] what I want to do is import this into our body or into the body of the layout component. So, I can
|
||||
[1338s] actually open bracket and start to type our header. And then oftentimes, I'll get IntelliSense for
|
||||
[1343s] this, but it looks like it's not opening for me. So, I can go and do a manual import up here instead.
|
||||
[1349s] So, at the top of this, we can import header from and then we'll go back a directory into the
|
||||
[1355s] components directory and then grab the header.astro. So, we can save this and we should now see the
|
||||
[1362s] basics of our application starting to come together. We have our header up here. We have a missing
|
||||
[1366s] icon up here, and we'll need to add that inside of our source code so that we can actually have this
|
||||
[1372s] show up. So, one of the things that we can do is go ahead and go to the astro course demo,
|
||||
[1379s] final source code, and then inside of the public directory, we have an images directory,
|
||||
[1384s] and we have a heartbeat.png, both of which we're going to need. So, we can click on the heartbeat.png,
|
||||
[1391s] and we can download this. So, we can download that file. And then additionally, we're going to need
|
||||
[1397s] the images directory as well. So, from the images directory, we can download this directory as well.
|
||||
[1405s] All right. So, that should download all of those files. And so, what you'll need to do is go and find
|
||||
[1410s] the heartbeat image and then that directory that we just downloaded, and we'll add that into our
|
||||
[1415s] source code. So, in this case, I'm going to take the heartbeat.png and I'm going to add this into the
|
||||
[1419s] public directory. So, there that is there. And if we look inside of our header, it's referencing
|
||||
[1425s] slash heartbeat.png, which should reference a heartbeat image right inside of that public directory.
|
||||
[1430s] So, now if we come back and go to our application and refresh, we should see that heartbeat
|
||||
[1436s] icon is starting to show up, which is great. Now, the other thing we wanted to do is take those
|
||||
[1440s] images that we downloaded in the zip folder and add those to the public directory as well.
|
||||
[1445s] So, inside of the public directory, I'm going to create an images folder. And I'm going to drag
|
||||
[1450s] all those images that we just downloaded into that folder. So, into the images directory here.
|
||||
[1456s] Now, one additional benefit of that, if we remember, is back in the layout component,
|
||||
[1461s] we defined a default image for our OG image to be inside of the images directory just like we did,
|
||||
[1469s] and in the band.jpeg. So, this now should be the default image that shows up for our OG image tags.
|
||||
[1475s] And actually, we can test this by going directly to this inside of the URL. So, we go to slash images,
|
||||
[1480s] and then slash band.jog, but jpeg. And we should now see this entire image showing up. So, we know that
|
||||
[1488s] that is working as well. So, we have our images copied over, which is exactly what we want. We have our
|
||||
[1494s] header showing up here above. We have links to our different pages, which we haven't created yet.
|
||||
[1500s] And now, we can go into our index component, that root page. And we can get rid of all of the styles.
|
||||
[1507s] So, we can delete all of these styles. And we can delete all of the main content that's in here as well.
|
||||
[1512s] So, let's just scroll all the way through and get rid of everything inside of main. And in this case,
|
||||
[1518s] we're going to update this title to be relevant to the blog post, the blog site that we're working on,
|
||||
[1525s] which is the rhythm nation blog. So, it's just the demo idea here. But, let's go ahead and type that
|
||||
[1532s] in rhythm nation blog. And now, we can add a title to this as well. So, I'm going to actually create a new
|
||||
[1539s] component for an h1 that we can reuse. So, we'll create an h1 component dot astro. I'm going to
|
||||
[1544s] copy over the tiny bit of code that we have for this, where we define our props. We take a text
|
||||
[1550s] property as our prop. We destructure that. And then we put it inside of an h1 that already has the tail
|
||||
[1556s] one styles created. So, I'm going to save this. And then back inside of the index, we can now reference
|
||||
[1563s] our h1 component. And we'll pass in a property of rhythm nation. And then we'll need to import this
|
||||
[1572s] component so that we can reference it. So, we'll import h1 from. And then we'll go into that
|
||||
[1577s] components directory. And then we'll grab the h1 dot astro. And now, we should see a basic title
|
||||
[1583s] showing up. Now, one thing you might notice is that we need some spacing on the outside of this.
|
||||
[1588s] So, one more component that we're going to create is the main dot astro component. And what we're
|
||||
[1593s] going to do is use this component to wrap all of the other things that we do. So, in this case,
|
||||
[1598s] I'm going to open up a main tag. And then we'll pass in everything in between as the slot. And then
|
||||
[1604s] we'll just add some tail one classes in here. So, we'll have a px of 24, which is padding x. We'll
|
||||
[1612s] set a max width of 7 xl. We'll set mx to be auto, which is going to automatically center everything
|
||||
[1618s] horizontally. And then we'll set the width to be full. And then lastly, we can set the padding to
|
||||
[1625s] be five on screens that are at max, small size. So, this is saying that we'll have a padding x of
|
||||
[1634s] five on screens extra small and small. And then above that, anything bigger will have a padding x of
|
||||
[1639s] 24. Now, the last thing we need to do, we can actually duplicate this import. We can import the main
|
||||
[1646s] component from that component's directory. And we can now use our main component instead of just
|
||||
[1652s] the main tag. And that should wrap everything and give some spacing on the outside. So, now we have
|
||||
[1657s] a clean section here for our main content that has that padding on the outside. We have our header,
|
||||
[1662s] we have our images loaded. And we can start to do more with this application by building out blog
|
||||
[1667s] functionality and leveraging the content collection feature in Astro, which is one of my favorite features
|
||||
[1672s] of Astro. Now, if we look at the final demo from the full Astro course, we can see we have a homepage
|
||||
[1678s] where we show a bunch of blog posts, we have tags, etc. But what I want to show you is that if we go
|
||||
[1683s] to the slash blog page, we can see a list of all these blog posts, which is what we're going to start to
|
||||
[1688s] work on now. So, we can see a list of all the blog posts in addition to all the images that are
|
||||
[1694s] associated with them. So, if we scroll through, we can see these blog posts. And then if we click on one,
|
||||
[1699s] we'll actually be taken to the specific route for the individual page. So, notice inside of the
|
||||
[1705s] URL, we have our route URL slash blog and then slash the title of that blog post in a slugified
|
||||
[1711s] version, which means having dashes in between all of the words. So, let's start to work on setting up
|
||||
[1717s] our application in this crash course to be able to work with Markdown using content collections in
|
||||
[1722s] Astro. So, let's go over to the Astro documentation and let's search for content collections. So, what
|
||||
[1729s] content collections are are ways to organize and manage and author content in any Astro project.
|
||||
[1735s] And in my personal opinion, this is the best experience for working with content specifically
|
||||
[1739s] Markdown and or MDX content that I've ever seen across any platform, which gets me really, really
|
||||
[1745s] excited. So, our content collections again give us a way to organize all these inside of a special
|
||||
[1751s] directory in Astro called content directory. So, under source slash content, we can then create a
|
||||
[1757s] directory for each different type of content that we want to create. In this case, their demo,
|
||||
[1761s] they have newsletter. In our case, we will have blog and that's where all of our blog posts will
|
||||
[1766s] live as Markdown files inside of there. Now, you can scroll down and find a lot more about this
|
||||
[1772s] with multiple collections, et cetera. But the one thing I do want to show you is how to define
|
||||
[1776s] collections inside of the content config file, which is a TypeScript file and allows you to define
|
||||
[1783s] types for your individual collections like the blog collection using ZOD to have TypeScript type
|
||||
[1791s] associated with each of your collections. So, you define a collection, you define a schema,
|
||||
[1796s] and you can define all of the different properties that are going to be associated with each piece of
|
||||
[1800s] content. Now, before we actually get into the code, the one thing we will need to do is go back to the
|
||||
[1806s] Astro course demo and we'll need to download some sample Markdown files that we haven't here to
|
||||
[1811s] reference inside of our application. So, inside of the Astro course demo, there is the source directory
|
||||
[1816s] and an inside of there just like we talked about is a content directory and an inside of there is the
|
||||
[1822s] post directory. So, what you'll want to do is go and download this entire directory of all of these
|
||||
[1828s] posts. Now, after you do that, you'll want to make sure to extract all of those and then we'll go inside
|
||||
[1835s] of our source directory. We'll now create our content directory and an inside of there will create
|
||||
[1841s] another new folder called Post and then we'll take all of that that we just copied and drag it into the
|
||||
[1849s] post directory. Now, one thing I did skip from copying over is the images directory that you can see
|
||||
[1856s] here, but that's something we'll come back to when we get into image optimizations. So, let's just take
|
||||
[1861s] a real quick look at what we have inside of this content. So, inside of here we have our front
|
||||
[1867s] matter at the top of each one of these Markdown files and we have an author, we have categories,
|
||||
[1872s] we have a date, we have whether or not this blog post is featured, we have a cover image to be
|
||||
[1877s] referenced and then we have a title. Now, this is all the front matter and what we're going to do with
|
||||
[1881s] content collections is define a data type that represents this and stores or gives us an
|
||||
[1888s] intelligence inside of our editor to know which of these properties to associate with our given
|
||||
[1893s] blog post specifically with a given collection, which in this case is our blog post. Now, at the bottom
|
||||
[1898s] of this, you can see all of the sample Markdown that's included here. So, this is just some getting
|
||||
[1903s] started Markdown. So, we have something to render. You could obviously go and create your own Markdown
|
||||
[1908s] with your own content if you wanted to. Now, one thing I do want to change is the reference to where
|
||||
[1914s] these images are stored. So, actually, I'm going to select this whole thing and I'm going to do
|
||||
[1919s] Command Shift F on Mac or Control Shift F on Windows and I'm going to change this slightly
|
||||
[1925s] and I'm going to get rid of this leading dot in each one of these blog posts or each one of these
|
||||
[1929s] Markdown files and we'll come back to that again when we talk about updating our images to work with
|
||||
[1935s] the image component that comes with Astro to optimize our images. But right now, I want this to point
|
||||
[1941s] to the public images directory where those different images are. Again, we'll come back to this
|
||||
[1945s] in a minute. So, we have our sample Markdown. Now, we need to go into the content directory and we
|
||||
[1952s] need to create our config dot TS file. So, let's start to work on defining a content collection inside
|
||||
[1959s] of this config file. Now, to start, we're going to import the define collection function and then the
|
||||
[1965s] Z for Zod from the Astro content import. So, this is giving a little bit of an issue saying it cannot
|
||||
[1975s] be found. This should be okay. So, as we go through this, we'll make sure to run it just to make sure.
|
||||
[1980s] And then from here, I want to define my post collection and this is going to call the define collection
|
||||
[1986s] function. We'll call this and we'll pass it a config object. Now, this config object will then have
|
||||
[1992s] a schema and we'll say that the schema is going to be a Z dot object, which is a function and we'll
|
||||
[2000s] pass that a configuration object as well. So, we're using Z dot object to say that this schema is going
|
||||
[2006s] to be an object and then now we can define the different properties that it's going to have. So, we
|
||||
[2011s] can define it to have a property of author and then using Z, which is Z, we can say where this is
|
||||
[2016s] going to be a string. Then we'll have a date, which in this case is also a string. We'll have an
|
||||
[2023s] image, which is a string. We'll come back to this in a minute and then we'll have a title, which
|
||||
[2028s] is a string as well. Now, the cool thing about Zod is that it has other data types that you can work
|
||||
[2034s] with where you can add a lot of customization on what exactly these types should look like. And then
|
||||
[2040s] lastly, what we want to do is export a variable called collections and this is going to be an object
|
||||
[2046s] and we'll say a key is going to be posts and then it's going to have a value of posts collection
|
||||
[2052s] and it's really important that this word here match up with the name of the directory that
|
||||
[2056s] that content is in. So, those two things should match, which means our post collection should be inside
|
||||
[2062s] of this post directory inside of content. So, now that we have our definitions for our content,
|
||||
[2067s] we want to start to query this content so that we can start to display this inside of our slash blog
|
||||
[2073s] page. So, to do this, we'll need to create another component inside of our pages directory and we're
|
||||
[2078s] going to create the blog dot astro component. Now, in here, we can start to query our content by
|
||||
[2085s] referencing the get collection function that comes from that astro content name space. So, this is going
|
||||
[2093s] to be a function that we can call to get the content associated with a specific collection. So,
|
||||
[2100s] in this case, we're going to sign this to a variable called posts. We're going to await a call to get
|
||||
[2106s] collection and then inside of a string, we're going to pass it the name of the content that we're
|
||||
[2110s] looking for. Now, notice it gives me an intelligence in here because it knows what the different content
|
||||
[2115s] collections are that I've defined. So, I can now query these posts inside of here and now we can be able,
|
||||
[2121s] we should be able to log the post to the console. So, important to note with astro is all of this code
|
||||
[2130s] is going to be run statically at build time. So, it won't quite look this way when we run this now,
|
||||
[2136s] but when this is deployed, all of this content is going to be queried and generated at build time
|
||||
[2141s] and then deployed statically. We'll talk more about this as we convert to SSR later in this video.
|
||||
[2146s] But in this case, we should be able to go back to our site. We should be able to click on the blog
|
||||
[2153s] page. Nothing will show up, but if we go back to our logs, we should see that this is actually querying
|
||||
[2160s] all of this content, which is pretty nice. So, what I want to do is start to be able to display
|
||||
[2165s] the basics here. So, one thing I'm going to do is copy over the structure of a page from that root page.
|
||||
[2172s] And now, we'll say this title is going to be blog and then rhythm nation, maybe not rhythm nation
|
||||
[2177s] blog because that's repetitive. We also have missing imports. So, I can do command and period and go to
|
||||
[2183s] add all missing imports. This would be control period if you're on Windows machine. Now, we have all of our
|
||||
[2188s] imports and then we can also update this to be blog. So, now we should at least have the basics of a
|
||||
[2194s] page kind of showing here. So, rhythm nation blog. So, that's great. But now we want to actually be able
|
||||
[2199s] to display that content. So, one of the things that we could do is we could iterate through our post.
|
||||
[2206s] So, we could say post. And then map and then get a reference to each post. And then for each post,
|
||||
[2212s] what do we want to return? So, inside of here, we could start with an h2 and then reference the post.
|
||||
[2222s] Data, that's going to be all of our front matter. And then inside of here, when we press enter,
|
||||
[2227s] we now get intelligence for all those properties, which in this case, I'm going to choose the title.
|
||||
[2231s] So, this is not going to look great, but at least we have the ability to show that all these post titles
|
||||
[2235s] are being queried here. Now, the other thing we might want to do is wrap this all in an anchor tag. So,
|
||||
[2241s] if we kind of stub out an anchor tag here and wrap our h2, what we want to do is we want to set the
|
||||
[2249s] href to a particular URL that will take the user to that blog post. So, in this case, we can define this
|
||||
[2257s] ourselves by using an ES6 template literal string. And we could say this is going to take the user
|
||||
[2262s] to slash blog slash. And then inside of our template literal string, we can reference the post.slug.
|
||||
[2269s] So, this is going to be the slugified version of that based on the name of that actual file.
|
||||
[2275s] So, now, each one of these should be a link to that blog post even though that page doesn't exist yet.
|
||||
[2281s] So, if we hover on this on the bottom left, you can see it links to slash blog slash blah blah.
|
||||
[2286s] If we click on this, it doesn't exist and that's our responsibility to go and create that.
|
||||
[2290s] So, we want to do a couple of things in here to make this look a little bit better. We'll cheat a
|
||||
[2294s] little bit and copy in some components to help us. We'll start with the post list.astro component.
|
||||
[2301s] And now, in this case, what we're going to do is define our props to take in a prop of post,
|
||||
[2308s] which is an array of a collection entry of the type of post. Now, again, post is going back to that
|
||||
[2315s] collection that we define. And we're just saying we have an array of those posts that we're passing
|
||||
[2321s] inside of here. Now, then we have our tailwind CSS to be able to display a CSS grid here with two
|
||||
[2328s] columns on bigger screens and then go down to one column on smaller screens. And then we display each
|
||||
[2333s] individual post with a post component that we haven't created yet. So, inside of our posts or inside
|
||||
[2339s] of components, we'll create one more component and this is going to be the post component that we can
|
||||
[2345s] paste in. Alright, so very similar. We define a prop in here where we're going to take one property,
|
||||
[2352s] which is a collection entry of posts. So, it's one post. We then destructure that and now we can
|
||||
[2358s] reference each piece of that data. So, notice we also have the same kind of link in here with an H2
|
||||
[2364s] where we have the post.data.title. And then we have the link that's linking to slash blog and then
|
||||
[2370s] the slug. We also are referencing the body of our blog posts, but we're using a few CSS or tailwind CSS
|
||||
[2377s] classes or one in here to say line clamp of two. This will give it a maximum line, maximum display of
|
||||
[2385s] two lines and then use ellipses to finish it out. And then at the top of this, we're also referencing
|
||||
[2390s] our image, which will come back to in a minute as we go and optimize these in a second. So, we can
|
||||
[2396s] save this. We can save the post list component. Let's go back to our blog page and let's get rid of
|
||||
[2401s] this log and just make sure all this stuff looks good. So, let's scroll to the bottom of these logs
|
||||
[2406s] and the terminal. There we go. And then now if we refresh our page here, nothing looks different because
|
||||
[2412s] we actually need to use that post list component. So, we'll replace the anchor tag that we wrote,
|
||||
[2418s] we'll reference the post list component and then we'll pass into that our post property that we
|
||||
[2424s] queried above. And then we'll need to also import this. So, I'm going to copy the layout import,
|
||||
[2431s] paste in post list, and then paste in our type in post list here as well. And this is from the
|
||||
[2439s] components, not the layouts directory. So, now we should see that we're actually loading each of
|
||||
[2446s] these posts and it's linking to the individual page for that post. So, notice this doesn't display
|
||||
[2451s] yet because we haven't generated those pages, but we do have the ability to link to each individual
|
||||
[2456s] blog post, which is pretty neat. Now, let's go ahead and generate the pages for each one of these.
|
||||
[2460s] Now, to do this, let's go to the Astro documentation really quick to kind of show you how we're going to.
|
||||
[2466s] So, we can search for dynamic routes. And in this case, what we do is define a file that basically
|
||||
[2472s] is going to have a placeholder in the file name that tells us some property that we can use to then
|
||||
[2478s] query and display the appropriate information for that post. Now, in our case, what we're going to
|
||||
[2484s] reference is the slug of the blog post. So, inside of our pages directory, we can create a new folder,
|
||||
[2490s] slash our blog. And then inside of here, we're going to create a new file that says inside of brackets,
|
||||
[2496s] slug, and then dot Astro. Now, what this means again is that we're going to be able to get the slug
|
||||
[2503s] for each one of these posts by defining each one of the different routes that we have. Now, the way we
|
||||
[2509s] define each one of these routes is we open up our JavaScript snippet here. And then we're going to
|
||||
[2514s] export a function called get static paths. This is going to be an async function. And then inside of
|
||||
[2521s] here, we want to query all of our posts. So, just like we did before, we'll call get collection.
|
||||
[2526s] And we'll pass in the post name. And we'll need to import that get collection function. So, we'll
|
||||
[2533s] import this get collection. And we'll also import collection entry from Astro content. And once we
|
||||
[2545s] have each one of our posts, what we want to you, what we want to do is use those posts to be able to
|
||||
[2550s] generate the path that should be created by Astro for each one of these different individual blog posts.
|
||||
[2555s] So, we're going to create a path's property. We'll take our post variable, we'll map through it,
|
||||
[2561s] we'll get a reference to each post. And then inside of here, we want to return an object. And
|
||||
[2568s] this is going to have a couple of different properties. The first one is params. So now inside of our
|
||||
[2574s] params, these are params that we can pass directly to this component. So, we want to pass in that
|
||||
[2579s] slug property. And it's going to come from post.slug. And then we want to pass in our props. So, our props
|
||||
[2586s] is going to be the post itself. So by exporting this get static pass function, we're basically
|
||||
[2592s] defining a path and a property for each one of these blog posts that will generate statically for
|
||||
[2599s] application. Now, from here, we can kind of define how this component is going to work. So, we'll define
|
||||
[2604s] the props type. This is going to have one property of posts that's going to be a collection entry.
|
||||
[2611s] It's also going to be referencing that post collection. So, we'll have one post is being passed in.
|
||||
[2617s] We can then destructure this. So, we can get the post from astro.props. And then from that post,
|
||||
[2625s] we can grab the contents. We can destructure the content itself from the post.render function,
|
||||
[2634s] which is an async function. Now, from here, we want to kind of lay out a blog post page,
|
||||
[2640s] just like we've done a few times before. So, let's copy over a few of these different components.
|
||||
[2645s] So, let's just paste this in. So, I have our layout. We'll have our main and we'll have our H1.
|
||||
[2651s] And we can import all of these at the top. So, we can import layout. And we can import the H1.
|
||||
[2659s] And lastly, our main. And so, we've imported all three of the components that we're going to use
|
||||
[2666s] in here. And just to start, we can now start to update a bit of information based on this individual
|
||||
[2671s] blog post. So, in this title, we want this to actually be the title of the blog post that we're on.
|
||||
[2676s] So, we can take post.data.title. And then inside of our H1 on the page, we can do the same thing.
|
||||
[2685s] So, we want to reference our post.data.title. So, what we should have done now is we should have
|
||||
[2691s] generated a page for each one of our blog posts that will, in this case, just display the title
|
||||
[2696s] of that blog post. So, if we click on one of these blog posts from the slash blog page,
|
||||
[2701s] it should take us to this page, but it looks like we have some sort of air in how we defined
|
||||
[2706s] our git static pass function. So, it is expecting an array, but got undefined. So, let's go back up
|
||||
[2712s] and double-check that. So, it looks like we defined our pass, but we didn't return this in the end.
|
||||
[2719s] So, the most important part about the git static pass function is it has to return those paths so
|
||||
[2724s] that Astro knows what to do with them to generate the individual pages. So, hopefully that will handle this.
|
||||
[2730s] Now, if we refresh, we see the title now coming up at the top and we have our individual blog
|
||||
[2736s] post pages for each of these individual blog posts. So, that's great. We have each of those defined.
|
||||
[2741s] We can now kind of copy a little bit of code from the post component. So, if we go back to the post
|
||||
[2747s] component and look at the image, we can now copy this end just so we're starting to display some
|
||||
[2753s] more things on here. So, if we paste this in under the H1, we should now see our images popping up.
|
||||
[2761s] So, it looks like it's not quite the size that we want, because we're now shrinking this to be a
|
||||
[2766s] width of 600. So, we can update this to be 1024. And I think that should give us now kind of the full
|
||||
[2773s] screen or almost full screen page that we're looking for. And then lastly, we can render this
|
||||
[2780s] content component that we got from the render function of our post. So, up here we called
|
||||
[2787s] post.render. We got the content component. We can render this, but it's not going to look great.
|
||||
[2792s] So, you can see we have all of our content in here, but this doesn't quite look great. And that's
|
||||
[2796s] because we don't have any styles to find for this. Now, in our case, what we're going to use is the
|
||||
[2801s] tailwind CSS typography package to handle this for us. So, tailwind CSS typography. You can search
|
||||
[2808s] this. Basically, what we're going to do is install this plugin. And then we'll be able to use this
|
||||
[2811s] inside of our page to be able to display this stuff appropriately. So, I'm going to copy this install
|
||||
[2817s] command. Let's go back, let's paste this in. And this is going to install the tailwind CSS typography
|
||||
[2825s] package. Then inside of our tailwind config, we need to make sure to reference this. So, we're going
|
||||
[2833s] to inside of this array require and then we're going to require the tailwind CSS typography package.
|
||||
[2843s] And then lastly, for this to work inside of where we render our content, we're going to need to wrap
|
||||
[2847s] this in a class or a div that has a few classes. Primarily, the pros and pros to Excel class.
|
||||
[2856s] So, those are the classes that kind of activate this extension or to be able to use that plugin to
|
||||
[2860s] be able to render all of our content. So, let's go ahead and run this with our run dev command.
|
||||
[2865s] We can now come back to our application. We can refresh this. And we should see that this now is
|
||||
[2870s] looking a lot better. And this is starting to feel like a real blog. So, we have, if we go back,
|
||||
[2875s] we have a list of all of our blog posts. And then we can click on the individual pages, see the image
|
||||
[2882s] and see all the content, which is pretty neat. Now, one thing that's interesting that's not very
|
||||
[2887s] optimized on here is the way we're referencing our images. So, if we go into our network tab
|
||||
[2893s] and just look at our images as they load in, we'll notice a couple of things. A couple of things.
|
||||
[2898s] We're loading all of these images even before we scroll down to see them. So, that's a little bit
|
||||
[2903s] unnecessary. It would be more optimal if we were only loading images as we're getting close to
|
||||
[2908s] scrolling down to them. And then we'll also see that these are being loaded as JPEG files,
|
||||
[2912s] which are not the most optimal format, where P would be a better format. And see that these are
|
||||
[2917s] really big images. So, six megabytes, four megabytes, et cetera. So, we can use the Asher image
|
||||
[2923s] component to make this a lot better and much more optimal. So, let's go back to the Asher documentation.
|
||||
[2928s] And let's just search for image. And let's just go to the top level images here. And let's go down to
|
||||
[2934s] the actual image component, which is what we're going to use to be able to do a lot of optimizations
|
||||
[2939s] with our images. So, we can import the image component from the image assets namespace and
|
||||
[2946s] basically just replace the regular IMG tag that we were already using. We'll have to do a few more
|
||||
[2951s] things in here, but let's start with that. So, inside of our post component, we can copy in the
|
||||
[2956s] import for this image component. And we can now replace that IMG with our image component.
|
||||
[2963s] Now, if we come back to our application, we can see that this is going to work,
|
||||
[2967s] but nothing is really changed. So, we're still loading all of these files and they're still JPEGs
|
||||
[2973s] and they're still pretty big. So, one of the things we want to do is actually move this images
|
||||
[2978s] directory into our content directory. And actually, specifically, we're going to move this into
|
||||
[2984s] our slash post directory because these are going to be all the images that are associated with these
|
||||
[2988s] posts. Now, then inside of our markdown, we're going to update. If you remember, we changed this
|
||||
[2994s] at the beginning, we're going to select this little bit and we'll do command shift F or control
|
||||
[2999s] shift F to select all of that. And what we want to do is we want to update this to go from slash to
|
||||
[3005s] dot slash. So, slash implies that it should look at the root of the application, which is at the end
|
||||
[3010s] of the URL. Dot slash now means we should look relative to where the actual file is. So, in this case,
|
||||
[3017s] it's going to be relative to where this markdown file is. And that's going to be inside of that post
|
||||
[3021s] directory. So, I'm going to update each one of these posts to reference dot slash image.
|
||||
[3028s] And we would think that would work, but we actually have an error in here of something isn't working.
|
||||
[3035s] And that's because we need to go back to our config for content collections. And we need to update
|
||||
[3041s] our image property to actually use an image object that Astro gives to us. So, what we're going to do
|
||||
[3047s] is turn instead of returning this object directly, we're going to turn this schema value into a function.
|
||||
[3055s] And so, this is going to then return that z dot object. But by defining this as an function,
|
||||
[3061s] we can now destructure a property called image. And now we can reference our image type to be of
|
||||
[3069s] type image or call that image function. So, this is going to more explicitly references as an image
|
||||
[3075s] in a way that Astro can understand in a way that it can also import those images from the content
|
||||
[3080s] or inside of the source content directory. So, let's try this one more time. Run this again.
|
||||
[3087s] So, the first thing that you notice, so you might notice, is that the URL for these images are looking
|
||||
[3092s] kind of weird. And that's because Astro is using kind of internal URLs to define how to render these
|
||||
[3098s] images. So, if you look really closely, you can see it defines the format and width and height, etc.
|
||||
[3104s] But we can see that these are loading web p images and that these are much smaller than what they
|
||||
[3108s] were originally. So, our page now is going to load much faster because these images are much more
|
||||
[3113s] optimized and they're a better format and they're much, much smaller. Now, if we click on one of these
|
||||
[3118s] individual pages, notice that this isn't working. And that's because we're referencing this using the
|
||||
[3123s] old image IMG tag, which can't be referenced here. So, let's go into the slug page. And let's just
|
||||
[3131s] update this to use the image component from Astro assets. And we should be able to just save this as
|
||||
[3137s] is and have this be working. So, now we're able to load this image and this should also be choosing
|
||||
[3143s] a web p version of this which should be smaller than what it was originally. So, now we have a working
|
||||
[3148s] list of all the blog posts. We can then go to the individual page for a blog post. We see an optimized
|
||||
[3154s] image and we can see all the content associated with that blog post as well. Now, with all this in place,
|
||||
[3159s] there's one really neat thing that we can add that's really easy and Astro and pretty amazing.
|
||||
[3163s] If we look at navigating between these pages, we see kind of this page refresh. We actually don't
|
||||
[3168s] have the about page created, but we see kind of the page refresh as we go between individual pages.
|
||||
[3174s] And we can actually make this a little bit easier by using the View Transitions API in Astro.
|
||||
[3179s] So, so, only take it a second to add, but it does make a big difference in how you can view and navigate
|
||||
[3185s] through your application. So, inside of the View Transitions API, you can you can read a lot more
|
||||
[3191s] about this. We can basically import this component and then use it inside of the head of any page that
|
||||
[3197s] we want to have those transitions between. So, since we want to by default use this on every single
|
||||
[3202s] page, we can actually just import this inside of our layout file. And then somewhere in the head,
|
||||
[3209s] we can just reference this View Transitions component and save. And now, we're actually going
|
||||
[3215s] to be able to see a difference between how we navigate our pages. This is pretty neat. So, let's go
|
||||
[3220s] from this page to home. Notice we get kind of the animation. We go to blog. We get animation.
|
||||
[3226s] We see this page. We get the animation. So, it looks like a much, much better experience of
|
||||
[3231s] navigating between pages with just one component that we can add. Now, there's some additional ways
|
||||
[3236s] that you can customize this. You can also pass state from one page to another, which is pretty neat.
|
||||
[3240s] We won't go any deeper into this, but it is nice to know that we can add this pretty easily to make
|
||||
[3245s] the transitions in our application look a lot better. Now, one additional thing I wanted to show you
|
||||
[3250s] is that you have the ability to not only use Markdown inside of Astero for your content, you can also
|
||||
[3256s] add MDX. And the support for this comes with an integration that we can add with one of those
|
||||
[3262s] NPX Astero add commands. So, let's go and add support for Markdown by pacing in this command.
|
||||
[3269s] This will make a change to the Astero config and install that package. So, we'll just go ahead and
|
||||
[3274s] say yes to all the things that it needs to do. Yes. All right, so that should be added.
|
||||
[3279s] And now we can do an MPM run dev to start this again. And what we should see, if we come back to our
|
||||
[3286s] Markdown files, for example, the behind the scenes, is we can now rename this file,
|
||||
[3293s] and we can rename this to an MDX file. And now, all of this should still stay the same. So,
|
||||
[3299s] if we come back to the application. So, if we refresh this, we see this stays the exact same,
|
||||
[3304s] which is exactly what we wanted. But now we can harness the power of MDX in addition to just the
|
||||
[3309s] Markdown that we were already using. Now, if we scroll back up, there's a quick section on Y MDX.
|
||||
[3315s] So, now, we're not going to dive deep into this, but there are lots of really cool things that you
|
||||
[3319s] can do, like MDX only features. So, you can use exported variables inside of MDX. So, if you wanted
|
||||
[3326s] to create variables at the top, you could then reference that. You can also use your front-matter variables,
|
||||
[3332s] so you could use those directly inside of here as well. And the last thing is you can reference
|
||||
[3336s] Astero components and UI components of other frameworks like React View, etc., inside of this as well.
|
||||
[3344s] So, if we look in the example in here inside of the MDX part, we're importing two different
|
||||
[3350s] components, one Astero component and one React component that we can then display right in line inside
|
||||
[3356s] of our Markdown content. Now, this is really useful as an example to do like a callout inside of your
|
||||
[3362s] blog post. If you want to customize a callout to send somebody to a newsletter or something else,
|
||||
[3367s] you could define those components and bring those into your MDX files anytime that you want or
|
||||
[3374s] need. So, we're not going to dive any deeper into MDX. That's kind of a section on its own,
|
||||
[3379s] but it is nice to know that you have the ability to work with both Markdown and MDX files in your
|
||||
[3384s] content with Astero. Now, we can start to work on deploying this application. So, we initialize this
|
||||
[3391s] initially as a GitHub or a Git repository. So, we can now do a Git status and we can see all
|
||||
[3398s] the things that we've changed. Now, in this case, we can add everything with Git add star,
|
||||
[3402s] then we can do a Git commit-m to say initial commit and all of this stuff has been committed to this
|
||||
[3410s] local Git repository. Now, the next step is we need to connect this to a GitHub repository that we can
|
||||
[3416s] then use to deploy to Netlify or Verselm. So, on GitHub.com, you can go to the top right. You can
|
||||
[3424s] click New Repository and then we can call this FCC Astro Crash Course Test. We'll have this be a
|
||||
[3434s] public directory. We don't want to add a readme because we'll take care of that ourselves. We don't
|
||||
[3438s] want to add a Git Ignore. So, we can create this very blank repository and then what we'll do is just
|
||||
[3444s] take the code into our terminal that pushes from an existing repository to our local Git repository
|
||||
[3453s] to this GitHub repository. So, you can copy this section where it adds the origin and the remote, or
|
||||
[3460s] it adds the remote origin and then pushes everything locally to that remote project inside of GitHub.
|
||||
[3468s] All right. So, it looks like it pushed all of that code up. If we come back to the GitHub repository,
|
||||
[3473s] we can come in here and see that this has been added. And so, now our next step is to go to,
|
||||
[3478s] we'll start with Netlify. We can do Netlify and Verselm. These are both free. So, you'll sign up with a
|
||||
[3482s] free account after you do. You can log in on Netlify. And basically, what we're going to do is add a
|
||||
[3487s] new site where we're going to import from an existing project. We'll choose to deploy with GitHub.
|
||||
[3493s] And then what we need to do is go and choose that project that we just created. So, I can search FCC
|
||||
[3498s] dash and this should be enough to pull up that project. All right. So, we can now pull this in.
|
||||
[3504s] We don't need to customize anything. It should pick up on what the build command is automatically.
|
||||
[3509s] So, we can go ahead and deploy this and it should run a build and then have this site ready for us to
|
||||
[3513s] use after it's done. All right. So, it looked like this build has finished. You can now kind of choose
|
||||
[3520s] the random URL that they gave you. And we should see that this is deployed our application successfully.
|
||||
[3525s] So, we can see our blog page. We can go and click on individual ones as well. So, that is on
|
||||
[3530s] Netlify. We could also choose to deploy this on Versel almost the exact same process. You'll sign up
|
||||
[3536s] for a free account so you can then come and add a new project. You're going to import this from a
|
||||
[3541s] Git repository. Choose from that FCC crash course project and GitHub. Choose all the defaults and then
|
||||
[3547s] go into deploy. And then after this is finished, we should have this deployed on Versel as well.
|
||||
[3554s] All right. So, it looks like this has finished on Versel. We can continue to the dashboard.
|
||||
[3558s] Then we can visit this at the random URL that it's generated for us also.
|
||||
[3562s] Now, so far everything that we've done with Astro has been statically generated pages.
|
||||
[3567s] Well, we can start to look into the SSR capabilities of Astro. So, Astro actually has the ability to do
|
||||
[3574s] a full backend if you so decide. And you have the ability to define what type of output your site
|
||||
[3581s] is going to have. So, by default, it is static, which takes no additional configuration for us to do.
|
||||
[3587s] But there also is the ability to define it as a server rendered application by default.
|
||||
[3591s] It says to use this one most or all of your site should be server rendered. You can also opt in to
|
||||
[3596s] pre-rendering or static pages for individual pages. You also have the option to do hybrid, which is
|
||||
[1:00:02] basically saying it's going to pre-rendered by default. And then you can define for individual pages
|
||||
[1:00:07] to opt out of pre-rendering. Before we make this transition into server-side rendering in our code,
|
||||
[1:00:13] let's actually take a couple of minutes to talk about the difference between static site generation
|
||||
[1:00:17] or SSG versus SSR, which is server-side rendering. And we'll talk about this while using diagrams to
|
||||
[1:00:23] kind of explain the differences between the two. So, let's start with what we've already been using,
|
||||
[1:00:27] which is SSG or static site generation. And this is what Astro does by default.
|
||||
[1:00:32] Quick reminder, if you want to follow up on this diagram later on, you can find the link in the
|
||||
[1:00:35] description below. So, what happens here is when we deploy our application, we deploy this
|
||||
[1:00:40] as something like Netlify or Versel, or there's lots of other hosts that you could use as well.
|
||||
[1:00:45] So, when this thing is deploying, it actually runs a build. And during that build process, what happens
|
||||
[1:00:49] is for each one of those individual pages that we have in our Astro application, it actually generates
|
||||
[1:00:54] the HTML file at build time for each one of those. So, we have one for our index.html page.
|
||||
[1:01:00] We have one for our blog.html page. And then additionally, we have an HTML page created for each
|
||||
[1:01:06] one of our blog posts. And that's where we define that export or we exported that get static pass
|
||||
[1:01:11] function, where we defined each one of the pass that we wanted to be able to support and then
|
||||
[1:01:16] to generate the content for. So, the important part about this is SSG at build time is going to go
|
||||
[1:01:22] ahead and create the content or the HTML pages for each one of these pages at build time so that it's
|
||||
[1:01:28] ready and accessible by the time someone comes and tries to view one of these blog posts for example
|
||||
[1:01:33] or one of these other pages. And that's an important next step to talk about is the actual request time.
|
||||
[1:01:38] So, what happens? Well, these individual files that are generated during the build process are then
|
||||
[1:01:43] saved to a CDN or a content delivery network that are replicated all across the world. What this
|
||||
[1:01:49] means is that those files now are very fast to access and return when a request comes in from the
|
||||
[1:01:55] browser. So, let's say that you go to the browser and you type in local host, but you type in
|
||||
[1:01:59] the URL of the application that you're trying to work on and you go to the index page. That's going
|
||||
[1:02:04] to make a request to the CDN. It's going to now return that index.html page. Now, let's say you then
|
||||
[1:02:09] want to go to the blog page. Well, you make another request to the CDN. The CDN now is going to return
|
||||
[1:02:14] blog.html. Or if you're going to one of the specific pages for an individual blog post, it's going
|
||||
[1:02:19] to return those pages as well. Again, because they've already been predefined. Now, this starts to
|
||||
[1:02:25] differ a lot when we look at SSR or server side rendering. So, I've got almost an empty diagram here
|
||||
[1:02:31] for SSR build time. So, with server side rendering, you're still going to have a build process to go through
|
||||
[1:02:36] and run all of your code. You may run test if you have them, but basically this is going to go through and
|
||||
[1:02:41] do the build of your application. And you may have some static pages. We'll talk about how to mix these
|
||||
[1:02:47] in a second. But, for the most part, what this is going to do is now kind of have that server configured
|
||||
[1:02:52] so that it can handle those requests as it comes in. So, notice there's no predefined HTML pages
|
||||
[1:02:58] that are already calculated for us. That means if we scroll down now that something has to happen
|
||||
[1:03:03] at request time when this request comes in from the browser. So, notice instead of having a CDN,
|
||||
[1:03:09] we now have an application server. So, requests will go from the browser to the application server.
|
||||
[1:03:14] Now, for this application server to respond back, most likely specifically in this case with our
|
||||
[1:03:19] blog post, individual blog post pages, it's going to need to get the information necessary for those
|
||||
[1:03:24] blog posts from the database. Now, in our case, we're not using a traditional database. We're using
|
||||
[1:03:29] embedded markdown in our source code, but it basically works the same way. So, let's say that we make
|
||||
[1:03:34] a request to slash blog slash blog dash one, for example, that's the website that we're trying to go to.
|
||||
[1:03:40] Well, this is going to now make a request to the database. Let's add a new piece of text in here.
|
||||
[1:03:45] And it's going to say, give me all the information that you have about blog one. So, from the database,
|
||||
[1:03:51] the database is going to return back the data for blog one to the application server. The application
|
||||
[1:03:57] server is now going to turn that back into an HTML page, which will look like if we add our corresponding
|
||||
[1:04:04] piece of text to your slash blog slash blog dash one dot HTML. We can move this up a little bit for
|
||||
[1:04:13] readability. So basically with SSR or server side generated pages or applications, every request that
|
||||
[1:04:20] comes in is going to go to an application server. It is it is then going to query the database or in
|
||||
[1:04:25] whatever format it is, which might be embedded markdown that will return the data. The application
|
||||
[1:04:30] server will then take that content and turn it into an HTML page that can be rendered on the browser
|
||||
[1:04:35] and viewed by the user. So that's a quick overview of the difference between SSG and SSR. Let's go back
|
||||
[1:04:41] to our code and start to make this work inside of Astro. So in this case, we can start by going into
|
||||
[1:04:48] the Astro config and we can choose the output property to be server. Now, if we try to run this,
|
||||
[1:04:54] we should see that this is going to break. And that's because we're doing a couple of things that
|
||||
[1:04:59] are specifically geared towards statically generated pages. So let's go to our running application and
|
||||
[1:05:05] refresh and we see that we now are having an issue on the individual blog posts pages. And that's
|
||||
[1:05:11] because the way that we're generating those pages is using this get static paths function. And
|
||||
[1:05:17] that doesn't exist inside of an SSR deployed application. Now one thing we could do is we could
|
||||
[1:05:24] look in the documentation and we could see how to define this as a pre rendered page. That means
|
||||
[1:05:30] it's going to generate this page statically. So if we add this at the top of this file and refresh,
|
||||
[1:05:35] this actually will go back to working as we expect. So now we see we have that blog post. We can go back
|
||||
[1:05:40] to all of them and go to another one, etc. But just for practice and kind of experience, let's go back
|
||||
[1:05:47] and get rid of the pre render and let's see what it would take to actually figure out how to generate
|
||||
[1:05:52] these pages inside of an SSR environment. Now in this case, what we're going to do is go back to
|
||||
[1:05:58] our page and we can actually get rid of this entire get static pass function. And we can
|
||||
[1:06:06] start at the top here. And most importantly, what we're going to do is destructure a property called
|
||||
[1:06:11] slug from astro.params. Now what astro was going to do is because of this slug definition up here,
|
||||
[1:06:18] it's going to pass this slug into the astro.params object to let us reference it and use that in here to
|
||||
[1:06:26] dynamically query that post from astro. We can also get rid of our definition for the post and our
|
||||
[1:06:33] prop types. And then what we're going to do is we're going to get that post from an awaited call
|
||||
[1:06:40] to get entry by slug. Now this is a function up here that comes from astro content. It's a function
|
||||
[1:06:47] that they give us and we can say what content collection we want to get this from, which in this case
|
||||
[1:06:52] is post. And then we can pass in the slug. Now in this case, it's going to throw an error or show us
|
||||
[1:06:57] the typescript error because slug could be potentially undefined. So we're just going to say this is
|
||||
[1:07:02] going to be a string so that we get our appropriate type in here. Now in this case, it's throwing
|
||||
[1:07:07] an error because it's saying that we might query for a post that also doesn't exist. And what we
|
||||
[1:07:12] could do, we could say if that post doesn't exist, we could do an astro redirect so we could
|
||||
[1:07:17] return an astro redirect to the slash four or four or four page just just to show that that thing
|
||||
[1:07:26] wasn't found. Now we could go and customize this and do anything that we wanted to handle it. But in
|
||||
[1:07:31] this case, this ought to be enough just to get this working and now have these dynamically generated
|
||||
[1:07:36] pages be dynamically generated with server side rendering instead of statically generated pages.
|
||||
[1:07:41] So if we go back, we should see that we have all these showing up in our blog index page and then
|
||||
[1:07:46] clicking on one should be able to show all the details for this blog post as well. So we've now
|
||||
[1:07:51] completely flipped how we're rendering these blog post pages. Now they're server side rendered and
|
||||
[1:07:57] what this means just to clarify is as the request comes into this URL, it's going to send a request to
|
||||
[1:08:02] the server, the server is going to query based on that URL, the individual blog post, return that back
|
||||
[1:08:07] and then use that to render the page that shows up on the screen as opposed to previously.
|
||||
[1:08:13] We had each of these pages generated statically at build time for all the blog posts that we have.
|
||||
[1:08:19] Now the cool thing about this is we can still go back and configure individual pages to be configured
|
||||
[1:08:25] as static. So as an example on the homepage, there's no reason that this shouldn't just be a static
|
||||
[1:08:31] page. So we can still export a const pre render variable that's set to true and that will mark this index
|
||||
[1:08:40] page as static. So if we go back here, this will be a static page versus this is server side rendered
|
||||
[1:08:48] and this individual page or all the individual pages for our blog post are server side rendered as well.
|
||||
[1:08:55] Now what I do want to show one more thing that you can do is when you have server side rendered
|
||||
[1:09:00] enabled, you can define API endpoints. So we can search for server endpoints in here and basically
|
||||
[1:09:07] what that allows us to do is have a file inside of our pages directory that just basically serves as an
|
||||
[1:09:13] API endpoint instead of actually returning an astro component. So if we go into our pages directory,
|
||||
[1:09:19] we could create a new folder slash or called API and then we could just create a test.ts. Now
|
||||
[1:09:26] notice this is a TS file instead of an astro component again, because it only runs on the server.
|
||||
[1:09:32] And if we look in here, just copy kind of the basic starter code that they give us, but we're not
|
||||
[1:09:36] going to reference any of this. So we can kind of get rid of all of this information about products.
|
||||
[1:09:43] And then we can return back an object with a message that says hello world. So this is how we define a
|
||||
[1:09:51] starter function for API endpoints. So in this case, we're also not referencing this parameter.
|
||||
[1:09:58] So what we defined is a get endpoint where we're basically just going to return hello world as JSON.
|
||||
[1:10:04] So we can save this. We can then go back to our application. We can then open the URL and go back
|
||||
[1:10:10] to the root and then slash API slash test. And we should get back that message with hello world.
|
||||
[1:10:16] Now, what's really cool is we can define all of our HTTP endpoints with this as well. So we could also
|
||||
[1:10:23] export a post function if we wanted to. We could return with the same thing. Now,
|
||||
[1:10:28] unfortunately, there's not a way to be able to test this inside of the browser, because the browser
|
||||
[1:10:33] can only send get requests. So I have the postman extension inside of VS code installed. So if you
|
||||
[1:10:38] wanted to follow along, you could install the postman extension. This has been kind of my default way
|
||||
[1:10:43] of doing testing APIs for a long time. But now they have the the VS code extension to go with it.
|
||||
[1:10:49] So we can create a new HTTP request and we can now send a post request to the same general idea.
|
||||
[1:10:56] So local host for three to one. And this will be slash API slash test. And we'll send that and we'll
|
||||
[1:11:03] get the same response that we just got back with the message of hello world. Now inside of handling our
|
||||
[1:11:08] post request, we could also destructure the request as well. And then we could get the body
|
||||
[1:11:16] from that request by calling a weight request dot JSON. And then we could just return this just to
|
||||
[1:11:24] show that we're actually getting it. So let's just return that body, which is going to be an object.
|
||||
[1:11:29] So we're destructuring this request. Notice we also don't have TypeScript types around this.
|
||||
[1:11:33] So we could define this a little bit differently. If we wanted to, I'm just going to copy in a new
|
||||
[1:11:38] kind of function definition here. So this is going to use an arrow function syntax where we define
|
||||
[1:11:44] now our post to be an API route so we can add the missing import for that. Now it's going to give us
|
||||
[1:11:51] intelligence for the request. So if we do dot, we can see all the things that we have access to there.
|
||||
[1:11:56] We also then can see things that we might have on our programs in here as well. So we can now save this
|
||||
[1:12:02] and what we should see is if we go back to our request, this is a post request, but it doesn't have a
|
||||
[1:12:07] body. So we can now inject in here a raw body with JSON and we could have an object and we could
|
||||
[1:12:15] have a property of name and we could say astro crash course. And then what this should return back with
|
||||
[1:12:23] is that same object that we can see down here. So we have the ability in astro to define any and all
|
||||
[1:12:30] kind of server capabilities that we wanted. We could handle form submissions. We could define API
|
||||
[1:12:34] endpoints for all of our different methods, which is really, really cool and really, really powerful.
|
||||
[1:12:38] So I think the only next step is to show how do we actually deploy this to Netlify and Versel now that
|
||||
[1:12:44] we're doing server side rendering. So inside of here, we have a plugin or an integration for both
|
||||
[1:12:50] Netlify and Versel. So I'm going to copy and paste this command. So here's the Netlify one.
|
||||
[1:12:57] And let's paste this in here for Netlify. And this is going to add that package and then it's going to
|
||||
[1:13:03] make an addition to our astro.config file to use Netlify as the host. So notice it says adapter is
|
||||
[1:13:10] Netlify. We'll say yes. And then if we look inside of the astro config, we should see that it's
|
||||
[1:13:16] referencing adapter Netlify here. So now what we want to do is we will add everything. We'll do a
|
||||
[1:13:24] git commit with a message of added SSR and deploy to Netlify. And then since this is already
|
||||
[1:13:34] connected to our Netlify site, we can push this and Netlify ought to automatically pick this up,
|
||||
[1:13:39] pick up that change. Let's just log back in. It should pick up that change automatically. And it
|
||||
[1:13:44] should be building a new version of that that now is going to be our server side rendered version.
|
||||
[1:13:50] So we'll let this go through our build and then we'll open this up to make sure everything looks
|
||||
[1:13:53] okay. Now as this is building one thing to notice, it's referencing Netlify functions actually
|
||||
[1:13:59] just missed it. But inside of building, you can see that it references deploying this to Netlify
|
||||
[1:14:03] function. So that's how it's actually able to deploy this. It looks like everything is complete. We
|
||||
[1:14:08] should be able to open this production deploy. Hopefully everything now continues to look okay just
|
||||
[1:14:14] like it did before. And we can see the individual blog post pages as well. Now next we'll need to do this
|
||||
[1:14:20] for Versel. So we can copy in that same command and then add in Versel. Now this is going to go
|
||||
[1:14:27] through that same process, add the Versel package, and then it's going to update the astro config to
|
||||
[1:14:32] reference that Versel adapter. So we can say yes to that as well. So now this is updated. We can add
|
||||
[1:14:39] everything again. We can commit and say hosting on Versel. Then we can push this. Now do note that
|
||||
[1:14:48] deploy in Netlify. We're still connected in Netlify. So this will kick off another build.
|
||||
[1:14:52] That next build will fail in Netlify. But what we do want to see is inside of Versel, we should see
|
||||
[1:14:59] that this is kicking off a new build in Versel. So we can see under the building tab that it's going
|
||||
[1:15:05] through and it's doing this. So when that finishes, we should see that we have the same deployed
|
||||
[1:15:11] application hosted now on Versel using SSR. All right. So it looks like it's finished. We should now
|
||||
[1:15:17] be able to visit this and we have the same experience where we can go to blog. We can see all the pages.
|
||||
[1:15:22] We can go to the individual page, etc. Now going back to the idea of SSR inside of astro,
|
||||
[1:15:30] one of the coolest things that you could do in addition to API endpoints and other things is you
|
||||
[1:15:35] could start to incorporate authentication into your applications. So you have the ability with astro to
|
||||
[1:15:40] create full-fledged full stack applications and you can do authentication here by referencing cookies
|
||||
[1:15:46] as an example. So you could track a session in a cookie for a user and you could gate pages to prevent
|
||||
[1:15:52] users from getting to certain pages if they're not logged in or if they don't have certain permissions
|
||||
[1:15:57] or anything like that with authentication in that full astro course. We actually build a basic
|
||||
[1:16:02] authentication strategy using SSR and astro as well as taking advantages of taking advantage of cookies
|
||||
[1:16:11] and then having that reference users that are saved inside of a data database. So another really
|
||||
[1:16:16] cool full stack implication or example of what you can build with astro. Now if you want a full
|
||||
[1:16:22] overview of what we build in that course, you can go to astrocourse.dev and it breaks down everything
|
||||
[1:16:28] that we're going to build inside of this full application, including all the topics that are
|
||||
[1:16:32] covered, the pricing, etc. So if you're interested in that, you can find that at astrocourse.dev.
|
||||
[1:16:38] All in all, I hope you're as excited about astro as a framework as I am. Obviously, I'm pretty
|
||||
[1:16:43] excited. So thanks for checking out this crash course and I really hope that you enjoyed it.
|
||||