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 `