diff --git a/.claude/commands/new-post.md b/.claude/commands/new-post.md
index d4cc1ad..b4f4382 100644
--- a/.claude/commands/new-post.md
+++ b/.claude/commands/new-post.md
@@ -26,6 +26,8 @@ Set `draft: true` so it doesn't publish until the user is ready.
Leave `tags: []` empty — the user will add tags as they write.
After creating the file, tell the user:
+
- The file path created
- To run `yarn dev` to preview it at `http://localhost:3000/blog/[slug]`
- To change `draft: false` when ready to publish
+- **Before publishing:** add at least one image to `images: []` — posts without an image fall back to the generic site banner for social sharing. Store images at `public/static/images/posts/YYYY/post-slug-name/` and reference as `/static/images/posts/YYYY/post-slug-name/image.png`.
diff --git a/CLAUDE.md b/CLAUDE.md
index 25f6fc4..28c1cda 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,7 @@
# DaveGoosem.com — Claude Code Context
## Stack
+
- **Framework**: Next.js 14 (App Router), TypeScript
- **Content**: Contentlayer2 — MDX files in `data/blog/` and `data/authors/`
- **Styles**: Tailwind CSS 3 (class-based dark mode, Space Grotesk font)
@@ -11,6 +12,7 @@
- **Analytics**: Google Analytics via Pliny
## Key Commands
+
```bash
yarn dev # start dev server
yarn build # production build + postbuild (RSS, search index)
@@ -18,29 +20,54 @@ yarn lint # ESLint with auto-fix
```
## Path Aliases (tsconfig.json)
-| Alias | Resolves to |
-|-------|------------|
-| `@/components/*` | `components/*` |
-| `@/data/*` | `data/*` |
-| `@/layouts/*` | `layouts/*` |
-| `@/css/*` | `css/*` |
+
+| Alias | Resolves to |
+| ------------------------ | ------------------------- |
+| `@/components/*` | `components/*` |
+| `@/data/*` | `data/*` |
+| `@/layouts/*` | `layouts/*` |
+| `@/css/*` | `css/*` |
| `contentlayer/generated` | `.contentlayer/generated` |
## Key Directories
-| Path | Purpose |
-|------|---------|
-| `app/` | Next.js App Router pages |
-| `components/` | Shared React components |
-| `layouts/` | Blog post layout templates |
-| `data/blog/` | MDX blog posts (39 posts) |
-| `data/authors/` | Author MDX profiles |
-| `data/siteMetadata.js` | Site-wide config (title, URL, socials) |
-| `public/static/images/` | Static image assets |
-| `scripts/` | postbuild.mjs (RSS + search index) |
+
+| Path | Purpose |
+| ----------------------- | -------------------------------------- |
+| `app/` | Next.js App Router pages |
+| `components/` | Shared React components |
+| `layouts/` | Blog post layout templates |
+| `data/blog/` | MDX blog posts (41 posts) |
+| `data/authors/` | Author MDX profiles |
+| `data/siteMetadata.js` | Site-wide config (title, URL, socials) |
+| `public/static/images/` | Static image assets |
+| `scripts/` | postbuild.mjs (RSS + search index) |
+
+## SEO & Structured Data
+
+The following are already implemented — do not duplicate or replace them:
+
+- **Canonical URLs** — set in `generateMetadata()` in `app/blog/[...slug]/page.tsx` via `post.canonicalUrl ?? computed URL`
+- **`BlogPosting` JSON-LD** — generated by Contentlayer in `contentlayer.config.ts` (includes `publisher`, `mainEntityOfPage`, `author`). Injected in `app/blog/[...slug]/page.tsx`
+- **`BreadcrumbList` JSON-LD** — injected alongside BlogPosting in `app/blog/[...slug]/page.tsx`
+- **`WebSite` JSON-LD** — injected in `app/layout.tsx` (covers all pages)
+- **Publisher logo** — uses `DaveGoosem.com_Logo_Black.png` (not the white variant) so it is legible on white backgrounds as Google requires
+- **Sitemap** — `app/sitemap.ts`, auto-generated, excludes drafts
+- **Robots** — `app/robots.ts`, auto-generated
## Constraints
+
- Do not add API routes — this is a static/SSG blog with no backend
- Do not add a database or server-side state
- Contentlayer2 auto-generates TypeScript types on `yarn build` / `yarn dev` — do not edit `.contentlayer/` manually
- ESLint uses flat config (`eslint.config.mjs`) — not `.eslintrc`
- External links require `target="_blank"` and `rel="noopener noreferrer"` (enforced by ESLint)
+
+## Gotchas
+
+**Contentlayer scans all of `data/`** — any plain `.md` file placed inside `data/` (e.g. a `CLAUDE.md`) must be added to `contentDirExclude` in `contentlayer.config.ts` → `makeSource()`, otherwise Contentlayer warns and skips it at startup.
+
+**Content Security Policy** — `next.config.js` contains a hand-written CSP string. If you add any external script, font, frame, or image source (e.g. a new analytics provider, embed, or CDN), add its hostname to the appropriate CSP directive in that file or the browser will silently block it.
+
+**Remote images** — `next/image` only proxies domains listed in `next.config.js` → `images.remotePatterns`. Currently only `picsum.photos` is allowed. Add new domains there before using external image URLs in posts or components.
+
+**Bundle analysis** — run `ANALYZE=true yarn build` to open the webpack bundle visualiser. Useful before committing large new dependencies.
diff --git a/app/CLAUDE.md b/app/CLAUDE.md
index 055345e..d443274 100644
--- a/app/CLAUDE.md
+++ b/app/CLAUDE.md
@@ -1,23 +1,43 @@
# App Router — Claude Code Context
## Route Structure
-| Route | File | Notes |
-|-------|------|-------|
-| `/` | `app/page.tsx` | Home (recent posts via `Main.tsx`) |
-| `/blog` | `app/blog/page.tsx` | Post listing |
-| `/blog/[...slug]` | `app/blog/[...slug]/page.tsx` | Individual post — slug from MDX filename |
-| `/blog/page/[page]` | `app/blog/page/[page]/page.tsx` | Paginated listing |
-| `/tags` | `app/tags/page.tsx` | All tags |
-| `/tags/[tag]` | `app/tags/[tag]/page.tsx` | Posts by tag |
-| `/about` | `app/about/page.tsx` | About page |
-| `/projects` | `app/projects/page.tsx` | Projects page |
+
+| Route | File | Notes |
+| ------------------- | ------------------------------- | ---------------------------------------- |
+| `/` | `app/page.tsx` | Home (recent posts via `Main.tsx`) |
+| `/blog` | `app/blog/page.tsx` | Post listing |
+| `/blog/[...slug]` | `app/blog/[...slug]/page.tsx` | Individual post — slug from MDX filename |
+| `/blog/page/[page]` | `app/blog/page/[page]/page.tsx` | Paginated listing |
+| `/tags` | `app/tags/page.tsx` | All tags |
+| `/tags/[tag]` | `app/tags/[tag]/page.tsx` | Posts by tag |
+| `/about` | `app/about/page.tsx` | About page |
+| `/projects` | `app/projects/page.tsx` | Projects page |
## Blog Post Rendering
+
Posts flow: MDX file → Contentlayer2 (generates typed object) → `app/blog/[...slug]/page.tsx` → layout component (e.g. `DaveLayout`).
-## SEO
-- Use `app/seo.tsx` utilities for metadata — do not write raw `` tags
-- `robots.ts` and `sitemap.ts` are auto-generated at build time
+## SEO & Metadata
+
+**For static pages** (`/about`, `/projects`, `/tags`, etc.) — use `genPageMetadata()` from `app/seo.tsx`:
+
+```ts
+import { genPageMetadata } from 'app/seo'
+export const metadata = genPageMetadata({ title: 'Page Title', description: '...' })
+```
+
+**For blog posts** — metadata is generated dynamically in `app/blog/[...slug]/page.tsx` via `generateMetadata()`. It reads from the post's Contentlayer fields directly — do not duplicate it elsewhere.
+
+**Do not write raw `` tags** — always go through the Next.js Metadata API or `genPageMetadata()`.
+
+**Structured data (JSON-LD)** — blog posts render two `