@teispace/next-maker
Scaffold a Next.js 16 app in one command.
An opinionated CLI that scaffolds Next.js 16 with TypeScript, Tailwind v4, Biome, Pino, Zod, Redux Toolkit, next-intl, and Vitest pre-wired. A manifest-driven `doctor` and `remove` keep installs in sync with the latest template.
the problem
Every Next.js project I touch starts the same way. create-next-app, then a long opening day spent wiring the things every real app needs but no starter ships — env validation, structured logging, state, i18n, a real test runner, a real linter, husky hooks that don't fight the team, a CI pipeline that mirrors what the husky hooks do, theme tokens that actually work in dark mode.
I'd done that opening day three times in one quarter when I started writing this CLI. Each time the result was slightly different, slightly worse than the last, and impossible to keep in sync once a real codebase grew on top of it. If the next project clones from the previous project, every footgun gets multiplied — and there's no path back to "what we'd build today" without a manual diff against a memory of the last time.
So I made the canonical version a CLI.
what init actually does
npx @teispace/next-maker init my-appThree minutes later you have:
- Next.js 16 App Router project with strict TypeScript, no
any, ESM-first. - Tailwind v4 with theme tokens already split between dark + light, no
tailwind.config.jsto wrangle (v4 reads@themefrom CSS). - Biome for lint + format + import sort. One tool, sub-second runs, no ESLint+Prettier two-step.
- Vitest + React Testing Library + jsdom.
renderWithProvidershelper for components that need Redux or i18n. - Pino logger with auto-redaction of sensitive keys (
token,password,authorization,cookie). All app code imports from@/lib/logger; nothing usesconsole.*. - Zod-validated env.
@/lib/envparsesprocess.envat module load and throws loudly on misconfig — better than discovering a missing var in production at 3am. - Redux Toolkit + redux-persist with typed hooks (
useAppDispatch,useAppSelector). - next-intl for i18n, with locale-prefix
neverso URLs stay clean. - Husky pre-commit (
env:sync+ lint-staged + type-check) and pre-push (ci:check+type-check+check:deprecated+ tests). The build is reserved for CI. - A
commit-msghook running commitlint with conventional-commits.
Every choice has a reason that's been argued against and re-argued. The README explains each.
the manifest
The interesting part isn't the init — it's keeping installs in sync over time.
Every file init writes is recorded in a manifest: the path, a stable hash, and the feature it belongs to (logger, env, redux, i18n, etc.). This makes two non-trivial commands work:
npx @teispace/next-maker doctorReads the local install, walks the manifest, reports drift. "Your tsconfig.json is two minor versions behind the template — here's the diff." "Your biome.json has a deprecated rule." "You're missing src/lib/env.ts — the env validator was added in v1.12." This is a linter for your scaffolding, not your code.
npx @teispace/next-maker remove i18nWalks the manifest in reverse for the i18n feature: deletes src/i18n/, removes next-intl and its config, strips locale-aware routing, deletes the [locale] segment of the App Router. It's the surgical opposite of init: every file the CLI added, gone, with the surrounding code stitched back to "as if i18n was never there."
This matters because most starters become useless after week one. You can't safely re-clone over an existing app, and you can't cleanly opt out of pieces you didn't end up needing. The manifest is what makes it possible to ship a feature, decide later you don't want it, and remove it without leaving scar tissue.
why a CLI and not a template repo
I tried template-repo first. The problem: a template repo is a snapshot. The day you fork it, you've forked a moment in time. Every improvement after that has to be back-ported by hand into every fork, and most don't get back-ported at all. The world ends up full of stale next.js starters with subtly broken Biome configs and out-of-date dependencies.
A CLI doesn't have that problem because it publishes. Every npx @teispace/next-maker init resolves to the latest published version. The template behind it (teispace/nextjs-starter) is what this very portfolio is built on, and every commit to it gets pulled into the next CLI publish via the build pipeline. The CLI and the reference app evolve together, and doctor lets users keep up.
the publish pipeline
The CLI's CI does three things:
- Runs the same quality gates the templates teach: Biome, type-check, deprecation check, tests.
- Bundles the latest template snapshot into the CLI's
templates/directory at publish time. Users always get today's template, not whatever was committed when the CLI itself was last touched. - Auto-bumps the version with conventional-commits + changesets, publishes to npm.
Result: shipping is one merged PR. The template gets a fix → CLI republishes within minutes → doctor on existing installs lights up the next day.
what I'd do differently
Two things in retrospect:
- The manifest format is too implicit. I infer feature ownership from file path patterns ("anything under
src/i18n/is the i18n feature"). It works today because the structure is mine, but if external contributors add features it'll bite. The next major version moves to an explicitfeatures.tsdeclaration where each feature lists its files. doctorreports drift but doesn't fix it. Currently it's read-only. There's a gnarly product question hiding in "auto-apply": users have local edits, those edits matter, and a naivedoctor --fixwould clobber them. The right answer is probably a three-way merge UI, which is real work.
what's next
- A
features add <name>command — the inverse ofremove. Currently you scaffold once and you're done; if you skippedreduxatinitand want it later, you fork the template by hand. The manifest already knows what to add. - A web-based init wizard at
maker.teispace.io. Some teams want to click through choices before downloading; a web wizard generates the rightinitinvocation. - More opinionated AI-tooling integration. The starter already ships a
CLAUDE.mddescribing the stack; the next iteration adds project-specific MCP server config.
If you want the short version: @teispace/next-maker is the CLI I needed three years ago and finally built. v1.19 publishes weekly; the template behind it is the same one this site runs on. Try it.