A Next.js 16 + Payload CMS 3 website for a box truck dispatching service. It serves a marketing front-end for owner-operators and small fleets, with a headless CMS backend for managing blog content, media, categories, and users.
| Layer | Technology |
|---|---|
| Framework | Next.js 16.2.9 (App Router) |
| CMS | Payload CMS 3.85.1 |
| Database | PostgreSQL via @payloadcms/db-postgres (hosted on Neon) |
| Rich Text | Lexical editor (@payloadcms/richtext-lexical) |
| Styling | Tailwind CSS v4 |
| Fonts | Geist (Google Fonts via next/font) |
| Languages | TypeScript, React 19 |
src/
├── app/
│ ├── (app)/ # Public-facing website
│ │ ├── page.tsx # Homepage
│ │ ├── layout.tsx # Root layout with SEO metadata
│ │ ├── robots.ts # robots.txt
│ │ ├── blog/[slug]/page.tsx # Blog detail page (hardcoded for now)
│ │ ├── service/
│ │ │ ├── page.tsx # Services overview page
│ │ │ └── load-booking/
│ │ │ └── page.tsx # Load booking sub-service page
│ │ └── components/ # Shared UI components
│ │ ├── SiteShell.tsx # Layout shell (Navbar/Footer routing)
│ │ ├── Navbar.tsx # Dual-mode navbar (absolute/flow)
│ │ ├── Hero.tsx # Homepage hero with truck image
│ │ ├── BlogSection.tsx # Blog preview section
│ │ ├── ... # Other section components
│ │ └── Footer.tsx
│ ├── (payload)/ # Payload Admin panel
│ │ ├── layout.tsx
│ │ ├── admin/... # Admin UI pages
│ │ └── api/... # REST, GraphQL routes
│ ├── sitemap.ts # Dynamic sitemap
│ └── my-route/route.ts
├── collections/ # Payload CMS collections
│ ├── Posts.ts # Blog posts (title, slug, rich content, FAQs, SEO fields)
│ ├── Categories.ts # Blog categories (name, slug)
│ ├── Media.ts # Uploaded images (alt text)
│ └── Users.ts # Auth users (email/password)
├── lib/
│ └── extract-headings.ts # Utility: parse Lexical rich text → headings, reading time
├── payload.config.ts # Payload CMS configuration
└── payload-types.ts # Auto-generated TS types
(app)— public marketing pages (home, services, blog)(payload)— CMS admin panel at/admin, API at/api
SiteShell.tsx dynamically switches layouts based on the current path:
- Blog pages:
Navbar("flow")+Footer("flow")(sticky, centered, slim) - Service pages:
Navbar+ children wrapped inLandingPageFrame - Default (homepage):
Navbar+FaqSection+CtaSection+Footer
Many components (Navbar, BlogSection, Navbar) have two rendering modes via a variant prop:
"absolute"— positioned hero-style withbb-*CSS classes (legacy Figma design)"flow"— standard document flow with Tailwind classes (responsive, modern)
| Collection | Purpose | Key Fields |
|---|---|---|
| Users | Admin auth | email, password (built-in) |
| Media | Image uploads | alt, file upload |
| Categories | Blog taxonomy | name, auto-slug |
| Posts | Blog content | title, slug, content (Lexical rich text), FAQs (array), featureImage, SEO fields (metaTitle, metaDescription, focusKeyphrase), status (draft/published), publishedDate, author, category |
The front-end pages show static mock data (blog posts, services, testimonials). The Payload CMS is wired up and running, but the public pages do not yet fetch from the CMS API — they render inline content arrays. This is a typical intermediate state before integrating CMS-driven content.
robots.txtvia/robots.ts(two copies — one in(app), one in rootapp/)sitemap.xmlvia/sitemap.ts- Comprehensive
metadataexport inlayout.tsx(Open Graph, Twitter, JSON-LD compatible) X-Robots-Tagheader set innext.config.ts
src/lib/extract-headings.ts provides helpers for Payload's Lexical rich text format:
extractHeadings()— walks the JSON tree to extract h2/h3 headings for table of contentsextractAllText()— concatenates all text nodescalculateReadingTime()— estimates read time at 200 wpm
| Route | File | Description |
|---|---|---|
/ |
(app)/page.tsx |
Homepage with Hero, StatePicker, Intro, Services Process, Why Us, Pricing, Blog, Testimonials |
/service |
(app)/service/page.tsx |
Services overview (8 service cards), contact form |
/service/load-booking |
(app)/service/load-booking/page.tsx |
Load booking subpage with benefits, process, FAQs |
/blog/[slug] |
(app)/blog/[slug]/page.tsx |
Blog detail (hardcoded article with sections, author meta) |
/admin |
Payload CMS admin | Managed by (payload)/admin/[[...segments]]/page.tsx |
/api/* |
Payload REST/GraphQL | REST, GraphQL, GraphQL Playground |
.envcontainsPAYLOAD_SECRETandDATABASE_URL(Neon PostgreSQL)next.config.tswraps the Next config withwithPayload()plugin + custom headerstsconfig.json— standard Next.js TypeScript configeslint.config.mjs— ESLint v9 flat configpostcss.config.mjs— PostCSS with Tailwind CSS v4
| Script | Command |
|---|---|
npm run dev |
Start Next.js dev server |
npm run build |
Production build |
npm run start |
Start production server |
npm run lint |
Run ESLint |
npm run generate:importmap |
Regenerate Payload import map |