diff --git a/.claude/commands/new-post.md b/.claude/commands/new-post.md new file mode 100644 index 0000000..b4f4382 --- /dev/null +++ b/.claude/commands/new-post.md @@ -0,0 +1,33 @@ +Create a new blog post MDX file for this Next.js + Contentlayer2 blog. + +The user will provide a title (or ask them for one if not given). Then: + +1. Convert the title to a kebab-case filename: `data/blog/kebab-case-title.mdx` +2. Use today's date in `YYYY-MM-DD` format +3. Ask the user for a one-paragraph summary if they haven't provided one +4. Create the file with this exact frontmatter structure: + +```mdx +--- +title: 'Title Here' +date: 'YYYY-MM-DD' +tags: [] +draft: true +summary: '' +layout: DaveLayout +images: [] +authors: ['default'] +--- + +Write your introduction here. +``` + +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/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..c5a9c62 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "additionalDirectories": [ + "c:\\Dave\\Projects\\DaveGoosem.github.io\\.claude" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..28c1cda --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +# 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) +- **Deployment**: Vercel +- **Package manager**: `yarn` — never use `npm install` or `npm run` +- **Comments**: Giscus (GitHub Discussions), configured via env vars +- **Search**: kbar (local `public/search.json`, regenerated on build) +- **Analytics**: Google Analytics via Pliny + +## Key Commands + +```bash +yarn dev # start dev server +yarn build # production build + postbuild (RSS, search index) +yarn lint # ESLint with auto-fix +``` + +## Path Aliases (tsconfig.json) + +| 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 (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 new file mode 100644 index 0000000..d443274 --- /dev/null +++ b/app/CLAUDE.md @@ -0,0 +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 | + +## Blog Post Rendering + +Posts flow: MDX file → Contentlayer2 (generates typed object) → `app/blog/[...slug]/page.tsx` → layout component (e.g. `DaveLayout`). + +## 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 `