# Blog 500 Error — Debug Report

**Agent**: backend-specialist
**Date**: 2026-03-21
**Priority**: High
**Status**: RESOLVED

---

## Summary

Individual blog post pages (`/blog/<slug>`) 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
```
