Next.js 16 · React 19 · Tailwind v4

A calm Next.js 16 starter, built for Claude Code

An opinionated starter that settles structure, data flow, and conventions, then hands your agent the CLAUDE.md rules to match. Single app or Turborepo monorepo.

$ npx zen-claude-starter

The small calls, already made

You inherit the decisions that slow a project down at the start.

Server-first

Server Components by default. Reach for the client when a component needs state, effects, or the browser.

Conventions, enforced

ESLint and Prettier settle imports, props, and types before review, so nobody spends a comment on them.

A clear data flow

Server actions read. Typed query factories and one mutation wrapper handle the client. You follow a path someone already walked.

No needless abstraction

Three plain lines beat a clever helper used once. Build what this feature needs and stop.

Tested where it counts

Cover utils, hooks, and server actions. Leave the markup free to change without breaking a suite.

Calm by structure

Every file has a predictable home, so you spend your attention on the feature instead of the wiring.

Rules Claude Code actually follows

Conventions your agent can read, in a shape the linter can enforce.

CLAUDE.md ships with the project
Every scaffold carries its own CLAUDE.md and .claude/rules/ covering that project's structure, data flow, and naming. The first prompt lands inside the lines.
The rules match the layout you chose
Scaffold a monorepo and the docs describe apps/web and packages/ui. Leave TanStack Query out and the data-access rules cover server actions, so nothing points at code you never installed.
Enforcement lives in the linter
Import order, prop order, alias imports, effect discipline. An agent that ignores the guidance fails pnpm check, and pnpm check gates CI.
Fresh containers work on the first try
A SessionStart hook installs dependencies when a remote Claude Code session boots. Checks and tests run on the first try rather than failing on a missing node_modules.

Scaffold an app or a monorepo

Start a project, then reach for the patterns you'll want on day one.

Start a project

Pick a layout, name it, then run the command anywhere. No clone needed.

$ npx zen-claude-starter my-app
  • Run it bare and it asks for the layout and dependencies
  • Copies the template, then starts fresh git history
  • Installs dependencies, ready for pnpm dev

Add a UI component

Vendor a shadcn primitive into components/ui with the new-york style, then compose it. Pass props and variants rather than restyling.

bash
pnpm dlx shadcn@latest add card dialog

Fetch and mutate with TanStack Query

Define a typed query factory beside the model, call it directly, and reach for the shared useAppMutation wrapper to write.

tsx
// hooks/posts/posts.queries.ts
import { queryOptions } from "@tanstack/react-query";

import { getPosts } from "./posts.actions";

export const postQueries = {
  all: () => queryOptions({ queryKey: ["posts"], queryFn: getPosts }),
};

// in a client component
const { data } = useQuery(postQueries.all());

const createPost = useAppMutation({
  mutationFn: createPost,
  successMessage: "Post published",
  invalidates: [postQueries.all().queryKey],
});

Validate with Zod

Keep a form's schema and its inferred type in the form file, where the coupling is obvious. Parse on submit for typed, validated input.

tsx
// components/contact/contact-form.tsx
import { z } from "zod";

const contactSchema = z.object({
  email: z.email(),
  message: z.string().min(1, "Required"),
});

type ContactInput = z.infer<typeof contactSchema>;

function onSubmit(values: ContactInput) {
  const result = contactSchema.safeParse(values);
  if (!result.success) return result.error;
  // result.data is typed and validated
}

Write a test

Co-locate it in a __tests__ dir beside the file. Cover utils, hooks, and server actions. Leave the markup alone.

tsx
// utils/__tests__/slugify.test.ts
import { describe, expect, it } from "vitest";

import { slugify } from "@/utils/slugify";

describe("slugify", () => {
  it("lowercases and hyphenates", () => {
    expect(slugify("Hello World")).toBe("hello-world");
  });
});

Questions worth asking first

Does this work with Claude Code?
Every project it scaffolds carries a CLAUDE.md and a .claude/rules/ directory covering that project's structure, data flow, and naming. ESLint holds the same line on import order, prop order, and effect discipline, so pnpm check fails when an agent drifts from it.
Single app or monorepo?
You choose when you scaffold. The monorepo splits the same code into apps/web and packages/ui under Turborepo, sharing @repo/eslint-config and @repo/typescript-config. Script names match the single-app layout, so pnpm check means the same thing in both.
Can I leave out TanStack Query, Zod, or Motion?
Yes. Those three are the only optional dependencies. Drop Query and the scaffolder also removes useAppMutation, the query hooks, and the QueryClientProvider, then rewrites the data-access rules in CLAUDE.md to describe server actions.
Do I need to clone the repo?
No. Run npx zen-claude-starter from any directory. It asks for a name, a layout, the dependencies you want, and where to put the project.
Can I switch layouts later?
No command does it for you. Moving a single app into a workspace means creating the package directories and relocating files by hand. Scaffold the monorepo up front if you expect a second app.
How do I update a project after scaffolding?
You don't. The scaffolder copies the code once and steps back. After that first commit the files belong to your repo, and later releases of the starter leave them alone.