KPBoards
Skip to main content
KPBoards

Web and mobile apps, shipped.

KPBoards — building web apps and mobile apps. Selected work, build-log teardowns, and hire-me for your next build.

Quick links

  • Home
  • Blog
  • Work
  • Open source
  • Stack
  • Services
  • Playbooks
  • About
  • Contact

Legal

  • Privacy notice
  • Terms of service
  • Cookie policy
  • Affiliate disclosure
  • Refund policy
  • DMCA / Copyright

Contact

hello@kpboards.com+84 901 430 110
Copyright 2026 KPBoards. All rights reserved.
Privacy noticeTerms of serviceCookie policy
Back to open source
Open SourceFeatured

nextjs-ecc-starter — Production-grade Next.js 16 boilerplate wired for Claude Code

A Next.js 16 + React 19 + React Compiler + Tailwind v4 + shadcn-style + RHF/Zod + Zustand starter, with @t3-oss/env-nextjs build-time env validation, Vitest 80% coverage gate, Playwright across 3 browsers, AgentShield, and tooling for Claude Code, Cursor, and MCP. Ships as a GitHub template — hit "Use this template" and you have a dev server up in minutes.

stars
1 stars
forks
0 forks
License
MIT
Primary language
TypeScript
Last commit
Last commit: 12 days ago
View on GitHubHomepage
nextjs-ecc-starter — Production-grade Next.js 16 boilerplate wired for Claude Code

On this page

  • Overview
  • Why I built it
  • Architecture
  • Installation
  • Usage
  • Roadmap
  • Links

Overview#

nextjs-ecc-starter is a batteries-included boilerplate for the Next.js 16 App Router. Every new Next project rebuilds the same set of choices: UI lib, form lib, env validator, state store, CI, security baseline. This repo makes those choices — opinionated, modern, audited — so day one is product code instead of scaffolding.

It's the SSR sibling of react-ecc-starter: same ECC (Everything Claude Code) philosophy, different platform — that one is Vite SPA, this one is Next.js App Router with Server Components by default, Server Actions, and ISR. The repo is registered as a GitHub template (is_template: true), so the official quickstart is "Use this template" rather than git clone.

Released 2026-05-16 — early but stable: from v0.1 it ships AgentShield's security baseline, Dependabot security updates, and a real Vitest 80% coverage gate (not just configured — actually enforced).

  • Build: Next.js 16.2 + React 19.2 + React Compiler (babel-plugin-react-compiler 1.0) — no manual useMemo / useCallback.
  • Routing: App Router, Server Components by default, route handlers api/health + api/contact, and a Server Action at actions/contact.ts.
  • Styling: Tailwind CSS v4 (@tailwindcss/postcss) + shadcn-style primitives (Radix avatar/dialog/dropdown/label/separator/slot + CVA + clsx + tailwind-merge + lucide + sonner) — primitives owned in-repo, no CLI.
  • Forms: React Hook Form 7 + Zod 3 + @hookform/resolvers — with a Server Action sample that validates client + server against the same schema.
  • State: Zustand 5 (persist + skipHydration) — sidesteps the classic SSR theme-mismatch flash.
  • Env: @t3-oss/env-nextjs in src/env.ts — missing vars fail the build instead of crashing the first request.
  • Tests: Vitest 2 (jsdom + Testing Library, 80% threshold) plus Playwright 1.49 across Chromium + Firefox + WebKit.
  • Security: ecc-agentshield 1.4 with a committed baseline, a hook that blocks NEXT_PUBLIC_* leaks, and a middleware.ts that sets security headers.
  • AI tooling: .claude/ (12 agents, 13 commands, 14 skills) + .cursor/ (mirrored rules) + .mcp.json — Claude Code, Cursor, and MCP all usable out of the box.

Why I built it#

Standing in front of an empty npx create-next-app, every new project re-litigates the same ten decisions: where do UI primitives come from, which form lib, runtime or compile-time validation, which env lib, which state store, how to test layouts, which browsers in CI, which security scanner, where the AI prompts live, conventional commits or not. That's 1-2 weeks of yak-shaving each time.

nextjs-ecc-starter commits to those decisions — strict enough for production, open enough to layer in your own domain logic without fighting the framework. The build-time env validation alone earns its keep: missing env vars fail the CI build, not the first production request with a 500.

The differentiator is the full Everything Claude Code integration: .claude/ with 12 agents + 13 commands + 14 skills, a mirrored .cursor/ rule set, and .mcp.json for MCP servers. Instead of configuring three agentic IDEs from scratch on every project, this repo ships with them — open Claude Code (or Cursor) right after clone, and the review/scaffold/debug patterns are already there.

One honest note: v0.3.0 was released on 2026-05-16 — the repo is fresh, star count is low. The foundation is audited (AgentShield baseline, 80% coverage gate, Dependabot), but the community is new. If you want a starter "a solo founder ships daily code on top of", this fits; if you need "thousands of users have battle-tested it", give it a few months.

Architecture#

Organised around App Router conventions, with a clean server/client boundary.

  • src/app/ — App Router: root layout.tsx, landing page, (examples)/ showcase (components, forms, data-fetching ISR-vs-polling side-by-side), actions/contact.ts Server Action, route handlers api/health + api/contact, plus sitemap.ts / robots.ts / manifest.ts / loading.tsx / error.tsx / not-found.tsx.
  • src/components/ — ui/ for in-repo shadcn-style primitives, layout/ for ThemeProvider + ThemeToggle + SiteHeader + SiteFooter + Container, plus error-boundary.tsx.
  • src/hooks/ — useDebounce, useMediaQuery, useMounted, useLocalStorage (no third-party utility-hook lib).
  • src/lib/ — utils.ts (cn()), format.ts, fetcher.ts (a typed wrapper that returns Rust-style Result<T> instead of throwing), logger.ts, validations/ for shared Zod schemas.
  • src/stores/ — use-theme-store.ts Zustand store with persist + skipHydration (avoids the theme-flash hydration mismatch).
  • src/env.ts — @t3-oss/env-nextjs with split server / client schemas, validated at build time.
  • middleware.ts (root) — sets security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy).
  • tests/ — Playwright E2E across Chromium + Firefox + WebKit, configured in playwright.config.ts.
  • .claude/ + .cursor/ + .mcp.json + AGENTS.md — the AI tooling layer, audited by AgentShield.

Path alias @/ points at ./src/ — no ../../. ESLint flat config (eslint.config.mjs) + Prettier 3 + husky 9 + lint-staged 15 + commitlint 19 (conventional commits, enforced on both pre-commit and commit-msg).

Security pipeline: security:scan runs AgentShield plus a custom shell wrapper in scripts/security-scan.sh; security:baseline regenerates .agentshield-baseline.json (~21 KB approved-finding snapshot). A hook blocks NEXT_PUBLIC_* when a server-only secret would leak into the client bundle.

Installation#

Get the project running locally in a couple of commands.

Use the GitHub template (recommended)bash
gh repo create my-app --template pldkhoi/nextjs-ecc-starter --public --clone
cd my-app
Or clone directlybash
git clone https://github.com/pldkhoi/nextjs-ecc-starter.git my-app
cd my-app
Install dependencies (wires Husky hooks)bash
bun install
Copy env + start the dev serverbash
cp .env.example .env.local
# set NEXT_PUBLIC_SITE_URL and NEXT_PUBLIC_APP_NAME
bun dev   # http://localhost:3000
One-time Playwright browser installbash
bunx playwright install

Usage#

Common patterns and snippets to get started fast.

Server Component with ISR (revalidate)tsx
// app/(examples)/data-fetching/page.tsx
import { Container } from '@/components/layout/container';

export const revalidate = 60; // ISR — regenerate at most every 60s

async function fetchStatus() {
  const res = await fetch('https://api.github.com/repos/pldkhoi/nextjs-ecc-starter', {
    next: { revalidate: 60, tags: ['repo-stats'] },
  });
  if (!res.ok) return null;
  return res.json() as Promise<{ stargazers_count: number; pushed_at: string }>;
}

export default async function DataFetchingPage() {
  const repo = await fetchStatus();

  return (
    <Container>
      <h1 className="text-2xl font-semibold">Repo snapshot</h1>
      {repo ? (
        <p>
          {repo.stargazers_count} stars · last push {new Date(repo.pushed_at).toLocaleString()}
        </p>
      ) : (
        <p>GitHub API unreachable.</p>
      )}
    </Container>
  );
}
Server Action contact form (RHF + Zod, shared schema)tsx
// src/lib/validations/contact.ts
import { z } from 'zod';

export const contactSchema = z.object({
  name: z.string().min(2).max(80),
  email: z.string().email(),
  message: z.string().min(10).max(2000),
});

export type ContactInput = z.infer<typeof contactSchema>;

// app/actions/contact.ts
'use server';

import { contactSchema, type ContactInput } from '@/lib/validations/contact';

export async function sendContact(input: ContactInput) {
  const parsed = contactSchema.safeParse(input); // re-validate server-side
  if (!parsed.success) return { ok: false, error: parsed.error.flatten() };
  // ... persist or forward to email provider
  return { ok: true };
}

// app/(examples)/forms/contact-form.tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { contactSchema, type ContactInput } from '@/lib/validations/contact';
import { sendContact } from '@/app/actions/contact';

export function ContactForm() {
  const form = useForm<ContactInput>({ resolver: zodResolver(contactSchema) });

  return (
    <form onSubmit={form.handleSubmit(async (values) => {
      const result = await sendContact(values);
      if (!result.ok) form.setError('root', { message: 'Failed' });
    })}>
      <input {...form.register('email')} type="email" />
      <textarea {...form.register('message')} />
      <button type="submit">Send</button>
    </form>
  );
}
Zustand theme store with skipHydration (no SSR flash)tsx
// src/stores/use-theme-store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

type Theme = 'light' | 'dark' | 'system';

interface ThemeState {
  theme: Theme;
  setTheme: (theme: Theme) => void;
}

export const useThemeStore = create<ThemeState>()(
  persist(
    (set) => ({
      theme: 'system',
      setTheme: (theme) => set({ theme }),
    }),
    {
      name: 'theme',
      skipHydration: true, // hydrate manually after mount to avoid SSR mismatch
    },
  ),
);

// src/components/layout/theme-provider.tsx
'use client';

import { useEffect } from 'react';
import { useThemeStore } from '@/stores/use-theme-store';

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    useThemeStore.persist.rehydrate(); // manual rehydrate after mount
  }, []);
  return <>{children}</>;
}

Roadmap#

  • Promote the auth recipes (NextAuth v5, Clerk, Lucia) from docs/auth-setup.md into opt-in branches you can clone directly.
  • DB recipes (Prisma + Postgres, Drizzle + Neon, Supabase) split into separate branches — pick one, clone, no dead-code cleanup.
  • Extract @kpboards/next-ui as a standalone npm package once both nextjs-ecc-starter and react-ecc-starter stabilise — primitives shared cross-project.
  • Derivative template next-ecc-starter-shop: Stripe checkout via Server Actions, product catalog, cart on Zustand.
  • Next 16 → Next 17 migration guide once the RC stabilises, with a codemod to auto-fix breaking changes.
  • ECC: add a /release-notes command that auto-drafts CHANGELOG entries from conventional commits.

Links#

  • github.com/pldkhoi/nextjs-ecc-starter
  • https://github.com/pldkhoi/nextjs-ecc-starter