What you'll learn

By the end of this you'll have a blueprint for standing up an SEO-first Next.js content site — the folder layout, typed content modules, metadata conventions, sitemap and robots setup, and seed-data pattern that lets you ship articles before committing to a CMS or database.

This is template notes, not a copy-paste repo. The decisions matter more than the boilerplate.

Who this is for

  • Developers building a blog, docs site, or media hub on Next.js App Router
  • Teams that want SEO and performance defaults without WordPress or a headless CMS on day one
  • Engineers who read the Baseline content hub case study and want the condensed template version

Skip this if you need multi-tenant authoring, workflow approvals, or non-developer editors on week one. Reach for a CMS; revisit this layout when you outgrow it.

What is the Next.js content site template? Plain English

It's a structural blueprint: how to organise routes, content types, and SEO metadata so adding a new article is "create a typed document, rebuild, done" — not "touch twelve files and hope canonicals still work."

Plain English: think of it as a floor plan for a house designed for people who write in Markdown and ship in Git, not a furniture catalogue.

Prerequisites

  • Next.js 14+ with App Router experience
  • TypeScript comfort
  • Basic SEO vocabulary (title, description, canonical, sitemap)

Setup from zero

Step 1 — Copy the module layout, not individual pages

The template centres on src/modules/content/:

src/modules/content/
  types.ts          # Zod schema + Content type + path helpers
  seed-hubs.ts      # seed documents (fallback content)
  rewrites/         # human-edited overrides per slug
  repository.ts     # DB reads (optional MongoDB)
  index.ts          # merge seed + rewrites + DB

Pages import from the content module; they don't embed article text. When you fork the template for your niche, replace seed-hubs.ts topics and rewrites — keep the schema and path helpers.

Step 2 — Wire routes to content types, not URLs you invent ad hoc

Each content type maps to a URL segment via typePathSegment:

export const typePathSegment: Record<ContentType, string> = {
  tutorial: "tutorials",
  resource: "resources",
  "case-study": "case-studies",
  // ...
};

A contentPath() helper builds canonical URLs from hub, type, slug, and tags. Routes call the helper; marketing never hardcodes paths in three places.

App Router structure at minimum:

app/
  resources/[slug]/page.tsx
  case-studies/[slug]/page.tsx
  ai/tutorials/[slug]/page.tsx
  sitemap.ts
  robots.ts

Step 3 — Replace brand tokens and seed posts

Swap site name, domain, default OG image, and author IDs in one config file (src/config/site.ts or similar). Replace seed slugs with your niche — keep five to ten posts per hub so index pages aren't empty. Run next build and fix schema validation errors before touching design.

Brand tokens and content ship together. A beautiful layout with thin placeholder copy still fails Lighthouse's "useful content" sniff test.

The mental model

The mental model for a Next.js content template is: content is data, routes are views, SEO is a function of the data.

Every page receives a typed Content object and renders it through a shared shell — title, excerpt, byline, body, related links. Metadata generation calls the same repository function as the page component. Sitemap entries derive from the same slug list as generateStaticParams. One source of truth; three outputs (HTML, <head>, sitemap).

When SEO breaks, you fix the data layer or the helper — not seventeen page files.

Key terms

Seed content — typed documents checked into Git as the baseline catalog before MongoDB or a CMS exists.

Rewrite — a richer override for a seed slug, merged at build time so human-edited prose replaces placeholder text.

generateStaticParams — Next.js hook that pre-renders dynamic routes at build time from a slug list.

Article shell — shared layout component for post pages: header, prose body, footer, JSON-LD.

Hub — top-level topic namespace (AI, developers, resources) with its own index and tag conventions.

Step-by-step: what the template includes

Hub index pages. Each hub gets a server-rendered index listing published items filtered by hub and type. Index pages need their own h1, intro copy, and metadata — not just a grid of cards.

Article shell with TOC. Long posts get a table of contents generated from h2 headings in the body. The TOC is server-rendered from parsed Markdown or MDX — not a client-side scroll spy on first paint.

sitemap.xml + robots.txt. app/sitemap.ts maps all published slugs through contentPath(). robots.ts allows crawlers and points to the sitemap. Filter status: "published" only — drafts never ship to production builds.

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const items = await listContents({ status: "published" });
  return items.map((item) => ({
    url: `${siteUrl}${contentPath(item)}`,
    lastModified: item.updatedAt,
  }));
}

Seed content with no database required. The site builds and deploys from seed + rewrites alone. MongoDB is optional — add repository.ts when you need dynamic queries, search, or editor workflows.

JSON-LD on post templates. Blog posts emit BlogPosting + BreadcrumbList structured data from the same Content fields as visible metadata. One object; template maps fields to schema.org.

Patterns

Schema-first content pattern — Zod validates every document at the repository boundary. Build fails on bad data, not at runtime in production.

Single shell pattern — one ArticleShell (or three variants by type) instead of per-slug layouts.

Compile-time rewrite merge — combine seed and rewrites when the content module loads at build time, not on each request.

Common mistakes

Hardcoding URLs in components. Use contentPath(). When you rename a segment, one map updates every link.

Client-fetching article bodies. Primary content must server-render. Client fetch hurts SEO and LCP.

Missing canonical on paginated indexes. Page 2 of a list needs a canonical strategy — usually self-referencing or pointing to page 1 depending on content strategy. Decide explicitly.

Empty hub indexes at launch. Ship at least five posts per hub or don't expose the hub in navigation yet.

Little tip

Add a readingTime field to your content schema and compute it at seed time, not runtime. Users and JSON-LD both benefit; you avoid parsing Markdown on every request for a word count.

Little tip

Keep one priority image per page max. The template should mark only the hero or LCP image with priority on next/image. Article pages with six priority images defeat the optimisation.

Troubleshooting

Build fails on Zod parse. One seed document has a missing seo.description or invalid date string. The error stack points to the slug — fix the seed or rewrite.

Sitemap includes 404 URLs. Slug exists in seed but route generateStaticParams filters differently. Align filters: same status and type rules everywhere.

OG previews broken. Set metadataBase in root layout. Relative OG image paths resolve wrong without it.

Duplicate titles in search results. Index and paginated list pages copied the homepage title. Give every route unique generateMetadata output.

Checklist

  • [ ] Content schema defined with seo, status, dates, authorId
  • [ ] typePathSegment map + contentPath() helper in use
  • [ ] Dynamic routes use generateStaticParams from published slugs
  • [ ] generateMetadata on every post and index route
  • [ ] sitemap.ts and robots.ts wired to siteUrl config
  • [ ] Article shell with single h1, byline, JSON-LD
  • [ ] Seed content covers each hub you expose in nav
  • [ ] Rewrites folder started for slugs needing human prose

Practice task

Fork the module layout into a fresh Next.js app. Create three content types (e.g. tutorial, resource, case-study), five seed posts total, and one rewrite override. Deploy to Vercel. Verify sitemap, canonical, and one JSON-LD block in Rich Results Test. You'll have copied the template decisions, not just the folders.

FAQ

MDX or Markdown strings in TypeScript?
Markdown strings in seed modules are fastest for v1. MDX when you need embedded components in body copy. Don't block launch on MDX setup.

When do I add MongoDB?
When you need full-text search, analytics-driven related posts, or non-git editors. Until then, seed + rewrites scales surprisingly far.

Do I need a CMS?
Not on day one if developers write content. Add CMS when marketing needs a UI or localisation workflows outgrow Git.

How does this relate to Baseline's actual codebase?
These notes describe the same architecture Baseline ships — typed modules, rewrite pipeline, optional Mongo. The case study goes deeper on what broke; this resource is the starter blueprint.

What to learn next

  • Building the Baseline content hub — full case study with failures and fixes
  • SEO content brief template — brief writers before they fill your schema
  • Lighthouse performance cheat sheet — keep content pages fast after template copy
  • [Building the Baseline content hub](/case-studies/building-baseline-content-hub)
  • [SEO content brief template](/resources/seo-content-brief-template)
  • [Lighthouse performance cheat sheet](/resources/lighthouse-perf-cheat-sheet)

Takeaways

The Next.js content site template optimises for SEO-heavy hubs with typed seed data, shared article shells, and metadata derived from the same source as the page body. Database and CMS come later; structure comes first.

Copy the module layout and path helpers, replace brand and seed content, and ship five real posts per hub before polishing design. The template works when content is data — not when every page is a one-off JSX file.

If you remember only one thing: one Content schema, one path helper, one article shell — then every new post is data entry, not a routing project. That's the whole template.