Entry point for AI coding agents (Claude Code, Cursor, Cline, Aider, Copilot Chat) working on serp-secrets.com.
Personal blog of Filippo Danesi covering SEO, AI search, AEO/GEO, and the future of search. Built as a static-rendered Next.js site with MDX content authored through Keystatic CMS.
- Live site: https://www.serp-secrets.com
- Stack: Next.js 16 (App Router) · React 19 · TypeScript · MDX · Keystatic
- Deployment: Vercel (
vercel.json) - Content: Markdown source of truth in
content/(no database)
.
├── app/
│ ├── (main)/ # Public-facing routes (home, archive, blog, categories, pages)
│ │ ├── blog/[slug]/ # Individual post page (MDX rendered via next-mdx-remote)
│ │ ├── archive/ # Full post archive
│ │ ├── categories/ # Category index + per-category listings
│ │ ├── about/ contact/ privacy/ cookie/
│ │ ├── layout.tsx
│ │ └── page.tsx # Home page
│ ├── api/
│ │ ├── keystatic/ # Keystatic CMS API handlers
│ │ ├── og/ # Dynamic OG image generation
│ │ └── search/ # Search index endpoint
│ ├── components/ # Shared React components (Header, Footer, MDX, JsonLd, …)
│ ├── feed.xml/ # RSS feed route
│ ├── keystatic/ # Keystatic admin UI mount point
│ ├── globals.css
│ ├── layout.tsx # Root layout
│ ├── robots.ts # Dynamic robots.txt (Next.js MetadataRoute)
│ └── sitemap.ts # Dynamic sitemap.xml
├── content/
│ ├── pages/ # Static MDX pages (about.mdx, …)
│ └── posts/ # Blog posts as .mdx with YAML frontmatter
├── lib/
│ ├── config.ts # `siteUrl` constant
│ └── posts.ts # Post loading, categories, TOC extraction
├── public/ # Static assets (images, logo, resume)
├── scripts/ # Build-time and one-off Node scripts
│ ├── generate-summaries.js # Runs in `npm run build` before next build
│ ├── fix-headings.js
│ ├── import-wordpress.js
│ └── export-to-linkedin.js
├── keystatic.config.ts # Keystatic schema (post fields, collections)
├── next.config.ts
├── tsconfig.json
└── package.json
lib/posts.ts— single source of truth for post loading. ExposegetAllPosts,getPostBySlug,getPostsByCategory,extractHeadings,slugify. Frontmatter typePostFrontmatterlives here.lib/config.ts—siteUrl(use this, never hardcode the domain).app/components/JsonLd.tsx— schema.org components:PersonJsonLd,WebSiteJsonLd,BlogJsonLd,BlogPostingJsonLd,BreadcrumbJsonLd,CollectionPageJsonLd. Reuse these instead of inlining JSON-LD.app/components/MDXComponents.tsx— overrides for MDX-rendered elements (links, headings, images).app/(main)/blog/[slug]/page.tsx— post page; usesMDXRemotewithremark-gfmandrehype-pretty-code.keystatic.config.ts— if you change post frontmatter, update both this and thePostFrontmatterinterface inlib/posts.ts.
- Posts live in
content/posts/<slug>.mdx; the filename is the slug. - Frontmatter (YAML):
--- title: "..." date: 2026-03-08 # ISO date; used for ordering and sitemap lastmod description: "..." # Used for meta description and OG tags: ["SEO Strategies"] # Tag strings; category mapping is by slugified tag draft: false # Drafts are filtered out of getAllPosts() image: "..." # Optional; falls back to dynamic /api/og summary: "..." # Optional; renders as TL;DR aside ---
- Categories are not free-form: they are defined in
lib/posts.ts(categoriesarray). A post belongs to a category when one of its tags slugifies to the category slug. - Use H2/H3 only inside post bodies — H1 is rendered from frontmatter.title by
PostHeader. - All headings are auto-id'd via
slugify()and exposed in the table of contents.
- All public pages live under the
(main)route group with a shared layout. /blog/[slug]/for posts (trailing slash, see sitemap)./categories/[slug]/for category indexes./sitemap.xmlis generated dynamically fromapp/sitemap.ts./robots.txt,/llms.txt,/llms-full.txt,/agent-permissions.jsonare static files emitted topublic/at build time byscripts/generate-aeo-files.js./blog/[slug].mdreturns the raw MDX source for AI ingestion (rewrite toapp/api/posts/[slug]/route.ts).
npm install
npm run dev # http://localhost:3000
npm run build # runs scripts/generate-summaries.js then next build
npm start # serve the production buildKeystatic admin: http://localhost:3000/keystatic (auth required in production).
- TypeScript everywhere; prefer typed exports.
- Use the
@/path alias (configured intsconfig.json) — no relative../../chains. - Import the
siteUrlfromlib/config; do not hardcodehttps://www.serp-secrets.com. - Posts are loaded synchronously at build time via Node
fs— never inside client components. - Server components by default. Add
'use client'only when truly needed (clipboard, search overlay, theme toggle). - Schema.org markup: extend
app/components/JsonLd.tsxrather than inlining<script type="application/ld+json">. - Static export friendly: avoid runtime-only Next features that break ISR / SSG.
- Node.js >= 18.
- No client-side data fetching for content — everything is statically rendered from
content/. - Do not commit drafts with
draft: false. - Posts must have a non-empty
description(used in OG and meta).
GET /robots.txt— explicitly allows ClaudeBot, GPTBot, PerplexityBot, Google-Extended, Amazonbot, ChatGPT-User.GET /llms.txt— structured index of posts grouped by category, with descriptions.GET /llms-full.txt— same index plus the full body of every post (single-fetch ingestion).GET /blog/<slug>.md— raw MDX of an individual post, served astext/markdown.GET /agent-permissions.json— declared interaction policy for automated clients.- Each post page exposes a "Copy for AI" button (
data-copy-ai) and a "View as Markdown" link.
- README:
README.md - Keystatic schema:
keystatic.config.ts - Post utilities:
lib/posts.ts - Sitemap:
app/sitemap.ts - Robots:
public/robots.txt(generated byscripts/generate-aeo-files.js) - AEO/agent file generator:
scripts/generate-aeo-files.js - AEO audit snapshot:
serp-secrets-aeo.json(in user home)