# Blog 500 Error — Debug Report **Agent**: backend-specialist **Date**: 2026-03-21 **Priority**: High **Status**: RESOLVED --- ## Summary Individual blog post pages (`/blog/`) returned a **500 Internal Server Error** because `src/pages/blog/[...slug].astro` used `getStaticPaths()` — a Static Site Generation (SSG) API that is **not valid in SSR (`output: 'server'`) mode**. --- ## Root Cause ### The Conflict | File | Setting | |------|---------| | `astro.config.mjs` | `output: 'server'` (full SSR, Node adapter) | | `src/pages/blog/[...slug].astro` | Used `getStaticPaths()` (SSG-only API) | **Astro's `getStaticPaths()` is only valid in `output: 'static'` mode.** When the server receives a request for `/blog/getting-started-with-astro`, Astro attempts to call `getStaticPaths()` at request time, which is unsupported and causes a runtime error propagated as a 500. ### Error Flow ``` GET /blog/getting-started-with-astro → Astro SSR handler invokes [...slug].astro → getStaticPaths() is called at request time → Astro throws: "getStaticPaths() is not available in server mode" → middleware.ts catches and re-throws (line 73) → 500 Internal Server Error returned to client ``` --- ## Files Examined | File | Finding | |------|---------| | `astro.config.mjs` | `output: 'server'` with `@astrojs/node` standalone adapter | | `src/pages/blog/[...slug].astro` | Used `getStaticPaths()` — incompatible with SSR | | `src/pages/blog/index.astro` | Correctly uses `getCollection()` at the top level (valid in SSR) | | `src/content/config.ts` | Schema is valid; `blog` collection properly defined | | `src/content/blog/*.md` | All 3 posts have valid frontmatter matching the schema | | `src/middleware.ts` | Re-throws errors from `next()` — confirms 500 path | --- ## Fix Applied **File**: `src/pages/blog/[...slug].astro` ### Before (broken SSG pattern) ```typescript import { getCollection, type CollectionEntry } from 'astro:content'; export async function getStaticPaths() { const posts = await getCollection('blog', ({ data }) => !data.draft); return posts.map((post) => ({ params: { slug: post.slug }, props: { post }, })); } type Props = { post: CollectionEntry<'blog'> }; const { post } = Astro.props; const { Content } = await post.render(); ``` ### After (correct SSR pattern) ```typescript import { getEntry } from 'astro:content'; const { slug } = Astro.params; if (!slug) { return Astro.redirect('/blog'); } const post = await getEntry('blog', slug); if (!post || post.data.draft) { return Astro.redirect('/404'); } const { Content } = await post.render(); ``` ### Key Changes - Removed `getStaticPaths()` entirely - Replaced `getCollection` import with `getEntry` - Reads `slug` dynamically from `Astro.params` at request time - Handles missing/draft posts with a redirect to `/404` - No `Props` type annotation needed (props come from params now) --- ## Why `getEntry()` is the Correct SSR Approach In SSR mode, Astro dynamically serves each request. The URL slug is available at runtime via `Astro.params`. `getEntry('blog', slug)` fetches a single content collection entry by its slug — this is the official Astro SSR pattern for content collections. --- ## Secondary Observations These are not the cause of the 500 but are worth noting: 1. **`index.astro` works fine** — it uses `getCollection()` directly in the frontmatter (not in `getStaticPaths()`), which is valid in SSR since it runs per-request. 2. **Content is valid** — all 3 markdown posts pass the Zod schema in `config.ts`. No frontmatter issues. 3. **Middleware correctly re-throws** — the error handling in `middleware.ts` was functioning as designed; the 500 originated from the page handler, not the middleware itself. 4. **Blog index not affected** — `/blog` (list page) was working correctly; only individual post routes were broken. --- ## Testing the Fix After deploying, verify the following URLs return 200: ``` GET /blog/getting-started-with-astro → 200 OK GET /blog/ai-transforming-business → 200 OK GET /blog/cloud-migration-guide → 200 OK GET /blog/nonexistent-slug → redirect to /404 ```