DEV Community

Cenk KURTOĞLU
Cenk KURTOĞLU

Posted on

I built 20 production Next.js 15 templates: the stack, patterns, and lessons

I'm a freelance developer. Over the past few years I've built a lot of client websites across different industries, and I kept rebuilding the same patterns — while the "templates" I borrowed online were more like design mockups than real starting points.

So I built 20 real, multi-page website templates and packaged them properly. Here's what I built, the architecture, and what I'd do differently.

The problem with most templates

A typical "Next.js template" in practice:

/
└── app/
    └── page.tsx   ← everything is in here
Enter fullscreen mode Exit fullscreen mode

One file. Hero, features, pricing, footer — all scroll sections in a single route. That's not a website, it's a brochure. The moment a client says "I also need an About page, a Services page, a Contact form that actually sends email, and a Blog" you're starting from scratch anyway.

What "complete" means

Each template ships with 5–15+ real routes (not scroll sections). The law firm template: /, /practice-areas, /attorneys, /case-results, /about, /contact + a shared /api/contact. The hotel: /, /rooms, /amenities, /gallery, /booking, /contact.

A shared layout with active-link nav:

// app/layout.tsx
import { Nav } from "@/components/Nav"import { Footer } from "@/components/Footer"export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Nav />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

Working contact forms (no dead Submit buttons):

const [status, setStatus] = useState<"idle"|"submitting"|"success"|"error">("idle")

async function handleSubmit(e: React.FormEvent) {
  e.preventDefault()
  setStatus("submitting")
  const res = await fetch("/api/contact", {
    method: "POST",
    body: JSON.stringify(formData),
    headers: { "Content-Type": "application/json" },
  })
  setStatus(res.ok ? "success" : "error")
}
Enter fullscreen mode Exit fullscreen mode

The /api/contact route validates and returns { ok: true } — a clean stub, ready to wire to Resend or any provider without touching the form.

The pattern I landed on: single-file config

Every template feeds from one file:

// src/lib/siteConfig.ts
export const siteConfig = {
  name: "Sterling Law Group",
  nav: [
    { label: "Practice Areas", href: "/practice-areas" },
    { label: "Our Attorneys", href: "/attorneys" },
    { label: "Contact", href: "/contact" },
  ],
  contact: { email: "info@sterlinglaw.com", phone: "+1 (555) 000-0000" },
  attorneys: [
    { name: "Jane Sterling", title: "Founding Partner", specialty: "Corporate Law" },
  ],
}
Enter fullscreen mode Exit fullscreen mode

Change the brand once, it updates everywhere. Add an attorney to the array, the team page updates. One file to touch when rebranding — no grep-replacing across 30 components.

The stack

Next.js 15 · React 19 · TypeScript (strict) · Tailwind CSS 4 · App Router. No third-party UI library — plain Tailwind so buyers tweak any class without fighting someone else's abstraction. tsc --noEmit and next build pass clean on all 20 (buyers run npm run build immediately — if it errors, refund + bad review).

Lessons from building all 20

  1. Form-stub pattern wins — validate + return { ok: true }; the buyer wires their email provider in 10 min.
  2. TS strict catches build-time bugs manual testing misses (missing keys, undefined config fields, server/client prop mismatches).
  3. next/image everywhere — buyers add real images; protect their Lighthouse score from day one.
  4. Mobile-first is faster than desktop-then-responsive and holds up better.
  5. Active-link state is always forgottenusePathname() in the Nav, trivial, always missing.
  6. Honest demo labels matter — the web3 token chart and the e-commerce cart are labeled "Demo data" / "Demo checkout — no real payment". Small thing, big trust difference.

What I'd do differently

  • Validate the config with Zod for buyer autocomplete + clear errors
  • Set up live demos from day one — the single biggest conversion factor for template products

Where to find them

All 20 live demos + source: https://cenkkurtoglu.com — single $49, all 20 for $299 (instant ZIP, commercial license, lifetime updates). Happy to go deeper on any pattern in the comments.

Top comments (0)