@teispace/next-maker
A CLI that scaffolds Next.js 16+ applications (TypeScript, Tailwind v4, Biome, Pino, Zod, Redux Toolkit, next-intl, Vitest) and co-generates feature-based architecture — pages, layouts, components, hooks, Redux slices, API services, locales, providers, env vars, and tests. Ships a manifest-driven doctor and remove for ongoing maintenance.
Installation
Using npx (Recommended)
npx @teispace/next-maker <command> [args] [options]
Global Installation
npm install -g @teispace/next-maker
next-maker <command> [args] [options]
Commands at a glance
| Category | Command | Purpose |
|---|---|---|
| Lifecycle | init [name] |
Create a new Next.js application |
| Lifecycle | setup [options] |
Add features to an existing project (retrofit) |
| Lifecycle | doctor [options] |
Diagnose drift against known feature manifests |
| Lifecycle | remove <feature> |
Reverse a feature install (uses the manifest) |
| Routing | page <name> |
Generate a page/route |
| Routing | layout <segment> |
Generate a nested layout.tsx |
| UI | component <name> |
Shared component with auto-wired barrel exports |
| State | feature <name> |
Full feature module (DDD) |
| State | slice <name> |
Redux Toolkit slice (auto-registers in rootReducer) |
| State | service <name> |
API service (axios/fetch, optional CRUD) |
| State | provider <name> |
Context provider with RootProvider auto-wiring |
| Config | env <NAME> |
Add an env var (schema + .env.example + .env) |
| Config | locale [code] |
Add a new language/locale |
| Code | hook <name> |
Custom React hook |
| Code | test <file> |
Sibling test stub for a component/hook/slice |
| Assets | favicon |
Generate favicon.ico (and optionally PWA / OG / Apple icons) from a source image |
Run npx @teispace/next-maker <command> --help for the full option list of any command.
Lifecycle commands
init — Create a New App
npx @teispace/next-maker init [project-name] [options]
| Flag | Effect |
|---|---|
-y, --yes |
Skip every prompt and bootstrap with the recommended production defaults (see below) |
--package-manager <pm> |
Override the package manager (npm | yarn | pnpm | bun). Works with or without --yes. |
# Interactive (default)
npx @teispace/next-maker init my-app
# One-shot opinionated install
npx @teispace/next-maker init my-app --yes
# One-shot with a different package manager
npx @teispace/next-maker init my-app -y --package-manager pnpm
--yes defaults — every architecture feature on, heavyweight integrations off:
| package manager | yarn |
| HTTP client | fetch |
| dark mode, redux, i18n, tests, react-compiler | on |
pre-commit hooks, commitizen, copy .env |
on |
| WebSocket (requires Redux), docker, GitHub Actions, bundle-analyzer, community files (CODE_OF_CONDUCT etc.) | off |
Anything you don't want? next-maker remove <feature> after init.
The starter at teispace/nextjs-starter is cloned via degit and trimmed to match your prompt answers. The cleanup step strips opted-out features so the generated project compiles end-to-end on first install.
Interactive prompts:
| Section | Prompts |
|---|---|
| Identity | project name, description, author, version, support email, package manager, GitHub repo / issues / homepage |
| Architecture | HTTP client (axios / fetch / both / none), dark mode, Redux Toolkit, WebSocket (requires Redux), i18n, testing, React Compiler, Bundle Analyzer |
| Tooling | community files (CODE_OF_CONDUCT, CONTRIBUTING, SECURITY), README, Docker, CI/CD, pre-commit hooks (Husky/Commitlint/Lint-staged), Commitizen, copy .env.example → .env |
| Templates | keep GitHub issue/PR templates? include react-secure-storage? |
Always shipped:
- Next.js 16+ with App Router
- TypeScript (strict mode)
- Tailwind CSS v4
- Biome (single-tool lint + format)
- Pino structured logger with redaction
- Zod-validated env schema in
src/lib/env/ - Feature-based DDD architecture
- Dual HTTP clients (
fetchClient+axiosClient) on a shared foundation: two entry points (@/lib/utils/httpuniversal,@/lib/utils/http/serverfor Server Components), automaticX-Request-Idcorrelation, singleparseApiErrorpipeline, cookie-mode auth by default, typed query params via{ params }. A build-time__bundle-sentinel__rejects future regressions of the universal/server split. - WebSocket transport — opt-in via the
wsprompt (requires Redux): typedsocket.io-clientwrapper, lazy singleton,useWsEvent/useWsEmit/useWsStatushooks, Redux bridge into a dedicated (non-persisted)wsReducer. Cookie-mode auth by default; browser-only (SSR throws). - Hardened security headers in
next.config.ts scripts/sync-env.tsandscripts/check-deprecated.ts(used by thevalidatechain)
setup — Retrofit a feature
Add a feature to an existing project (one that wasn't generated by init, or one created before a feature existed).
npx @teispace/next-maker setup [options]
| Flag | What it does |
|---|---|
--http-client |
Adds the axios and/or fetch Result-based clients under src/lib/utils/http/ |
--dark-theme |
Installs @teispace/next-themes and adds CustomThemeProvider |
--redux |
Redux Toolkit + react-redux + redux-persist, StoreProvider, src/store |
--ws |
WebSocket transport — socket.io-client@^4.8.3, src/lib/utils/ws/ subtree, non-persisted wsReducer, attachWsBridge mount in StoreProvider. Requires --redux to be installed first. |
--i18n |
next-intl + [locale] routing + proxy.ts + RootProvider wiring |
--tests |
Vitest + React Testing Library + jsdom + test/test-utils.tsx |
--react-compiler |
reactCompiler: true in next.config.ts + babel-plugin-react-compiler |
--bundle-analyzer |
Wraps the default export with withBundleAnalyzer and adds the analyze script |
--security-headers |
Injects the hardened headers block into next.config.ts (DNS prefetch, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, X-XSS-Protection) |
--validate-scripts |
Drops in scripts/sync-env.ts + scripts/check-deprecated.ts and wires env:sync, check:deprecated, type-check, and the validate chain (PM-aware) |
--commitizen |
Writes .czrc, adds commit script, installs commitizen + cz-conventional-changelog |
Run setup without flags for an interactive picker.
# Interactive
npx @teispace/next-maker setup
# Specific feature
npx @teispace/next-maker setup --redux
npx @teispace/next-maker setup --ws # adds WebSocket layer (run after --redux)
npx @teispace/next-maker setup --security-headers
npx @teispace/next-maker setup --validate-scripts
Every setup operation is idempotent — re-running is a no-op on already-installed parts and a fix-up on missing parts.
doctor — Drift detection
doctor walks every feature manifest, asks "is this installed?" and (if yes) "are all of its parts present?". Drift is reported per feature.
npx @teispace/next-maker doctor [options]
| Flag | Behaviour |
|---|---|
--fix |
Runs each drifted feature's repair path, then re-checks and reports FIXED / STILL DRIFTED / NO AUTOMATIC FIX AVAILABLE per feature |
--feature <id> |
Only check one manifest (e.g. redux, security-headers) |
--json |
Machine-readable output for CI |
Exit code is 0 on a clean report and 1 when drift is found — next-maker doctor --json makes a useful CI gate. With --fix, the exit code reflects the state after the repair: 0 only when every feature re-checks clean, 1 when anything is still drifted.
# Human report
npx @teispace/next-maker doctor
# Fix everything that drifted
npx @teispace/next-maker doctor --fix
# CI
npx @teispace/next-maker doctor --json > health.json
Sample output:
🩺 Project Doctor
✓ Security Headers
! Validation Scripts (2 issues)
• missing file: scripts/sync-env.ts
• missing script: validate
— Redux Toolkit (not installed)
✓ Internationalization
1 clean, 1 drifted, 1 not installed
Run with --fix to re-apply drifted features.
Sample --fix output:
🔧 Applying fixes...
✓ Validation Scripts FIXED
✗ Internationalization STILL DRIFTED
• missing file: src/app/[locale]
1 fixed, 1 still drifted
--fix never claims success it can't back up: every feature is re-checked after its repair runs, and only a clean re-check counts as FIXED.
Manifests live under src/manifests/; each one declares the files, packages, scripts, and code blocks the feature consists of. Adding a manifest for a new feature is a single file — doctor and remove automatically pick it up.
remove — Reverse an install
Symmetric to setup. Uses the feature manifest to compute the reversal: deletes generated files, strips code blocks (when a removePattern is recorded), removes scripts, uninstalls packages.
npx @teispace/next-maker remove <feature> [options]
| Flag | Behaviour |
|---|---|
--dry-run |
Print the planned changes without writing |
-y, --yes |
Skip the confirmation prompt |
Auto-removed when possible: provider chain unwraps (<NextIntlClientProvider>, <StoreProvider>, <CustomThemeProvider>), the withNextIntl(...) and bundleAnalyzer(...) wraps in next.config.ts, and their orphan import statements. The transforms are conservative — if the file shape has drifted from the canonical pattern (e.g. a multi-line opening tag, no matching close at the same indent), the runner bails out and surfaces it as manual cleanup rather than corrupting your code.
remove will never recursively delete a directory that may hold user-authored content — src/app/[locale]/ (your pages), src/i18n/ (your translations), src/store/ (your slices), src/lib/utils/http/ (your service code), and test/ (your helpers) are flagged in the manifest with containsUserContent: true and surface as manual-cleanup with a hint. Move what you want to keep, then rm -rf the rest by hand.
# Preview the effect
npx @teispace/next-maker remove redux --dry-run
# Apply with confirmation
npx @teispace/next-maker remove security-headers
# Non-interactive
npx @teispace/next-maker remove validate-scripts --yes
Sample plan:
🗑 Remove Validation Scripts
Planned changes:
- delete file: scripts/sync-env.ts
- delete file: scripts/check-deprecated.ts
- remove script: env:sync
- remove script: check:deprecated
- remove script: validate
- uninstall: tsx
Code generators
page — Generate a page/route
npx @teispace/next-maker page <name> [options]
| Flag | Effect |
|---|---|
--dynamic <param> |
Dynamic segment (e.g. --dynamic id produces [id]/page.tsx) |
--loading |
Generate loading.tsx |
--error |
Generate error.tsx |
What it does:
- Writes
page.tsxundersrc/app/[locale]/<name>/(i18n) orsrc/app/<name>/ - When i18n is detected:
generateMetadata,setRequestLocale,getTranslations - Registers route in
src/lib/config/app-paths.ts - Adds translation namespace to
en.json
npx @teispace/next-maker page about
npx @teispace/next-maker page dashboard --loading --error
npx @teispace/next-maker page products --dynamic id --loading --error
layout — Generate a nested layout
npx @teispace/next-maker layout <segment> [options]
| Flag | Effect |
|---|---|
--group |
Wraps the segment in parens for a route group, e.g. (marketing) |
--at <path> |
Places the layout under a nested path (kebab-case, slash-separated) |
--no-locale |
Skip the locale wrapper even when i18n is detected |
When i18n is detected the generated layout consumes params: Promise<{ locale: string }> and calls setRequestLocale. Otherwise it's a plain ({ children }) => <>{children}</> shell.
# /[locale]/dashboard/layout.tsx
npx @teispace/next-maker layout dashboard
# Route group: /[locale]/(marketing)/layout.tsx
npx @teispace/next-maker layout marketing --group
# Nested: /[locale]/dashboard/settings/preferences/layout.tsx
npx @teispace/next-maker layout preferences --at dashboard/settings
Refuses to overwrite an existing layout.tsx — explicit error rather than silent loss.
component — Shared component
npx @teispace/next-maker component <name> [options]
| Flag | Effect |
|---|---|
--client |
Adds 'use client' directive |
--i18n |
Imports useTranslations |
--feature <path> |
Generate inside a feature directory |
--test / --no-test |
Co-generate a sibling *.test.tsx (default: on when Vitest is installed) |
Generated structure:
src/components/common/MyButton/
├── MyButton.tsx
└── index.ts
Auto-updates src/components/common/index.ts and src/components/index.ts.
npx @teispace/next-maker component data-table --client
npx @teispace/next-maker component nav-bar --client --i18n
npx @teispace/next-maker component user-card --client --feature src/features/auth
feature — Full feature module
npx @teispace/next-maker feature <name> [options]
| Flag | Effect |
|---|---|
--store <type> |
Generate Redux store: persist or no-persist |
--skip-store |
Skip Redux store |
--service <client> |
API service: axios or fetch |
--skip-service |
Skip API service |
--path <path> |
Custom path (default src/features) |
Generated structure:
src/features/user-dashboard/
├── components/UserDashboard.tsx
├── hooks/useUserDashboard.ts
├── types/user-dashboard.types.ts
├── store/ (optional)
├── services/ (optional)
└── index.ts
npx @teispace/next-maker feature user-profile --store persist --service axios
npx @teispace/next-maker feature shopping-cart --store no-persist --skip-service
npx @teispace/next-maker feature auth --store persist --service fetch --path src/modules
slice — Redux slice
npx @teispace/next-maker slice <name> [options]
| Flag | Effect |
|---|---|
--persist / --no-persist |
Toggle redux-persist for this slice |
--path <path> |
Custom path (default: new feature) |
--test / --no-test |
Co-generate *.slice.test.ts (default: on when Vitest is installed) |
Auto-registers in rootReducer with correct imports and persist config.
npx @teispace/next-maker slice auth --persist
npx @teispace/next-maker slice user-settings --path features/auth/store
service — API service
npx @teispace/next-maker service <name> [options]
| Flag | Effect |
|---|---|
--axios / --fetch |
Pick the HTTP client |
--crud |
Generate full CRUD (getAll, getById, create, update, delete) |
--path <path> |
Custom path |
CRUD mode also generates <Name>Summary (list view) and <Name>Detail (detail view) types, Create<Name>Dto, Update<Name>Dto, and registers the endpoints in app-apis.ts.
Endpoints are emitted as bare paths (e.g. '/users', `/users/${id}`) — the /api/v{n} prefix is owned by getApiBaseUrl() in src/lib/config/api-url.ts and applied at request time. Don't add ${API_PREFIX} interpolation in app-apis.ts; the base URL composer handles it.
npx @teispace/next-maker service payment --axios
npx @teispace/next-maker service users --fetch --crud
npx @teispace/next-maker service orders --axios --crud --path features/products/services
provider — Context provider
npx @teispace/next-maker provider <name>
Generates src/providers/<Name>Provider.tsx with:
'use client'directivecreateContext+ typed context valueuseX()hook with non-null guard<XProvider>component memoising the value withuseMemo
Then two-tier auto-wiring:
- Locates
RootProvider.tsx— canonical path first (src/providers/RootProvider.tsx), heuristic scan as fallback (looks for the deepest{children}chain insrc/providers/*.tsx). - Wraps
{children}with<XProvider>inside that chain, preserving indentation. - Updates
src/providers/index.tsbarrel (re-exports kept sorted).
If no candidate is found, the generator prints the snippet to wire manually rather than failing silently.
npx @teispace/next-maker provider auth # → AuthProvider, useAuth
npx @teispace/next-maker provider session # → SessionProvider, useSession
npx @teispace/next-maker provider analytics-provider # already-suffixed names work
The wrap is idempotent — running twice is a no-op.
env — Declare an env var
Adds a new variable across the four files the starter keeps in sync, atomically and idempotently:
src/lib/env/schema.ts— Zod entry with the right wrapper (preprocess(emptyStringToUndefined, …)for non-enum types).env.example—KEY=placeholder with optional description comment and# -publicmarker.env—KEY=<default>line when the file exists
npx @teispace/next-maker env <NAME> [options]
| Flag | Effect |
|---|---|
--type <type> |
string (default), url, number, boolean, enum |
--required |
Skips .optional() and .default() |
--default <value> |
Adds .default(value) (mutually exclusive with --required) |
--public |
Auto-prefixes NEXT_PUBLIC_ if missing and tags .env.example with # -public |
--describe <text> |
Adds .describe() and a comment line in .env.example |
--enum <list> |
Comma-separated values, required when --type=enum |
# Optional URL
npx @teispace/next-maker env SENTRY_DSN --type url --describe "Sentry endpoint"
# Public default
npx @teispace/next-maker env API_URL --type url --public --default "http://localhost:3000"
# Required string (no default, no .optional)
npx @teispace/next-maker env DATABASE_URL --type url --required
# Enum
npx @teispace/next-maker env LOG_LEVEL --type enum --enum debug,info,warn,error --default info
# Coerced number
npx @teispace/next-maker env PORT --type number --default 3000
The generator quote-escapes defaults and descriptions, mirrors the starter's house style, and is fully covered by tests against the schema.ts/.env.example/.env shapes.
locale — Add a locale
npx @teispace/next-maker locale [code] [options]
| Flag | Effect |
|---|---|
--copy-translations |
Copy English translations as the starting point |
Creates src/i18n/translations/<code>.json, updates SupportedLocale, and adds the entry to src/lib/config/app-locales.ts (name, flag, country).
npx @teispace/next-maker locale es
npx @teispace/next-maker locale fr --copy-translations
hook — Custom React hook
npx @teispace/next-maker hook <name> [options]
| Flag | Effect |
|---|---|
--client |
Add 'use client' directive (default true) |
--feature <path> |
Generate inside a feature directory |
--test / --no-test |
Co-generate *.test.ts |
npx @teispace/next-maker hook auth-session
npx @teispace/next-maker hook user-profile --feature src/features/auth
test — Retrofit a sibling test
Adds a *.test.{ts,tsx} next to existing code that pre-dates the --test flag.
npx @teispace/next-maker test <file> [options]
| Flag | Effect |
|---|---|
--kind <kind> |
Override inferred kind: component / hook / slice |
--force |
Overwrite an existing test file |
Inference rules:
*.slice.ts→ reducer testuse*.ts(or content starting withexport function useX) → hook test (renderHook)*.tsx→ component test (renderWithProviders)
npx @teispace/next-maker test src/features/auth/components/LoginForm.tsx
npx @teispace/next-maker test src/features/auth/store/auth.slice.ts
npx @teispace/next-maker test src/hooks/use-debounce.ts --kind hook --force
Requires setup --tests first.
favicon — Generate icons from a source image
Generates favicon.ico (multi-size) into the App Router root, with optional icon.png, apple-icon.png, opengraph-image.png, twitter-image.png, and PWA manifest icons. Source can be PNG, JPG, JPEG, WebP, SVG, or AVIF.
npx @teispace/next-maker favicon [options]
| Flag | Effect |
|---|---|
--path <file> |
Source image. If omitted, you'll be prompted. |
--out <dir> |
Output directory (default: auto-detected src/app or app) |
--icon |
Also emit icon.png (512×512) |
--apple |
Also emit apple-icon.png (180×180) |
--og |
Also emit opengraph-image.png (1200×630) |
--og-source <file> |
Use a separate image for the OG/Twitter card |
--twitter |
Also emit twitter-image.png (1200×600) |
--all |
Shorthand for --icon --apple --og |
--pwa |
Detect PWA setup and emit manifest icons (192/512); errors if not detected |
--pwa-init |
Bootstrap public/manifest.webmanifest with icons |
--shape <shape> |
square | rounded | circle | squircle (default: square) |
--radius <percent> |
Corner radius % when --shape=rounded (0–50, default: 20) |
--bg <color> |
Background: transparent, hex (#0f172a), CSS name, or rgb()/rgba()/hsl()/hsla() (default: transparent) |
--padding <percent> |
Padding around source content (0–30, default: 0) |
--fit <fit> |
contain | cover | clip (default: contain) |
--sizes <list> |
Comma-separated ICO sizes (default: 16,32,48) |
--quality <n> |
PNG compression 1–100 (higher = smaller; PNG is lossless, default: 90) |
--force |
Overwrite existing files without prompt |
--dry-run |
Show what would be written without writing |
# Minimal: just favicon.ico from a source PNG
npx @teispace/next-maker favicon --path ./brand/logo.png
# Full set: favicon + icon + apple-icon + OG image, rounded with brand background
npx @teispace/next-maker favicon \
--path ./brand/logo.svg \
--all \
--shape rounded --radius 24 \
--bg "#0f172a" \
--padding 8
# PWA: emit manifest icons and bootstrap public/manifest.webmanifest
npx @teispace/next-maker favicon --path ./brand/logo.png --pwa-init
# Separate OG art
npx @teispace/next-maker favicon \
--path ./brand/logo.png \
--og --og-source ./brand/social-card.png
# Preview without writing
npx @teispace/next-maker favicon --path ./brand/logo.png --all --dry-run
Outputs land in your App Router root (src/app/ or app/) so Next.js auto-resolves them via the file conventions for favicon.ico, opengraph-image, and apple-icon — no <head> wiring required.
End-to-end examples
Quick start
npx @teispace/next-maker init my-project
cd my-project
# Routes & layouts
npx @teispace/next-maker page dashboard --loading --error
npx @teispace/next-maker layout dashboard
npx @teispace/next-maker page products --dynamic id --loading --error
# State + data
npx @teispace/next-maker feature users --store persist --service fetch
npx @teispace/next-maker service users --fetch --crud --path features/users/services
# Providers & env
npx @teispace/next-maker provider auth
npx @teispace/next-maker env SENTRY_DSN --type url --describe "Sentry endpoint"
# i18n
npx @teispace/next-maker locale es
# Health check before commit
npx @teispace/next-maker doctor
npm run dev
Feature-driven workflow
npx @teispace/next-maker feature products --store persist --service axios
npx @teispace/next-maker service products --axios --crud --path features/products/services
npx @teispace/next-maker page products --dynamic id --loading --error
npx @teispace/next-maker component product-card --client --feature src/features/products
npx @teispace/next-maker layout products
npx @teispace/next-maker feature cart --store persist --skip-service
npx @teispace/next-maker page checkout --loading --error
Maintenance — re-applying drifted features
# What's drifted?
npx @teispace/next-maker doctor
# Fix in place
npx @teispace/next-maker doctor --fix
# Pull a feature out cleanly
npx @teispace/next-maker remove i18n --dry-run
npx @teispace/next-maker remove i18n
Architecture
Layers
src/
├── commands/ # Commander definitions (one file per CLI command)
├── prompts/ # Enquirer flows (one per command needing interactivity)
├── generators/ # generate<X>(): writes files for a single artifact
│ └── templates/ # Pure string-template functions (testable in isolation)
├── modifiers/ # Pure functions that surgically edit existing files
├── pipelines/ # PipelineStep[] composers for multi-step generators
├── services/setup/ # Retrofit installers, one per feature
├── manifests/ # Declarative feature footprints (drives doctor + remove)
├── detection/ # "Is feature X installed?" detectors
├── core/ # File I/O + package-manager helpers (yarn/npm/pnpm/bun)
└── config/ # PROJECT_PATHS, PACKAGES, spinner, output, error handlers
The manifest pattern
Each feature has a manifest in src/manifests/<feature>.manifest.ts describing:
- detect — high-level "is this installed?"
- files — paths the feature owns (with
generated: true/falseto control deletion safety) - packages — runtime/dev dependencies
- scripts —
package.jsonscript entries (with optional exact-value match) - injections — code blocks in shared files (with
presence, optionalremovePattern, and optionalalternativeFileswhen the block's home depends on the project shape — e.g.[locale]/layout.tsxorsrc/app/layout.tsx; the block only has to be in one of the candidates that exist) - apply —
withRepair(setup<X>, repair<X>). Called with no drift it installs from scratch (setup); called with a drift array it takes the repair path (doctor --fix), fixing exactly the reported findings without consulting the installer's "already set up?" guard - remove — optional custom remover (defaults to the generic reverser)
The manifest registry is consumed by:
setup— callsapply()directlydoctor— walks every manifest, reports drift viacheckManifestremove— runsreverseManifest(or a customremove) and prints the plan
Adding a manifest for a new feature is a single file. The CLI commands don't need to know about it.
Modifier conventions
- Pure transformation modules (e.g.
headers.ts,package-modifier.ts,env-var.modifier.ts) take strings or JSON and return strings or JSON. No filesystem. - Thin async wrappers (
index.tsper setup service, the modifier orchestrators) handle I/O and spinners. - Idempotency is required: re-running anything is a no-op when the target state is already reached. This is what makes
doctor --fixsafe.
Project structure (generated app)
The full shape of a scaffolded app (branches marked (opt) are stripped when the matching prompt is declined during init):
my-project/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── [locale]/ # (opt, i18n) locale-aware routes
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ ├── error.tsx
│ │ │ └── not-found.tsx
│ │ ├── global-error.tsx
│ │ ├── not-found.tsx
│ │ ├── robots.ts
│ │ ├── sitemap.ts
│ │ ├── favicon.ico # `favicon` command
│ │ ├── icon.png # (opt, `favicon --icon`/`--all`) 512×512
│ │ ├── apple-icon.png # (opt, `favicon --apple`/`--all`) 180×180
│ │ ├── opengraph-image.png # (opt, `favicon --og`/`--all`) 1200×630
│ │ └── twitter-image.png # (opt, `favicon --twitter`) 1200×600
│ ├── proxy.ts # (opt, i18n) Next 16 middleware replacement
│ ├── features/ # Feature modules (feature-first DDD)
│ │ └── counter/ # (opt, redux) example feature
│ │ ├── components/
│ │ │ ├── Counter.tsx
│ │ │ └── Counter.test.tsx # (opt, tests)
│ │ ├── hooks/useCounter.ts
│ │ ├── store/ # slice + selectors + persist + barrel
│ │ └── types/counter.types.ts
│ ├── components/
│ │ ├── common/ # Shared UI (auto-wired barrel exports)
│ │ └── index.ts
│ ├── providers/
│ │ ├── RootProvider.tsx # Composes Store → Theme → Intl → custom
│ │ ├── StoreProvider.tsx # (opt, redux) useRef + PersistGate
│ │ ├── CustomThemeProvider.tsx # (opt, dark-mode) @teispace/next-themes
│ │ └── index.ts # Barrel — `next-maker provider <name>` keeps it sorted
│ ├── store/ # (opt, redux) makeStore, rootReducer, typed hooks
│ │ └── slices/ws.slice.ts # (opt, ws) non-persisted connection state slice
│ ├── services/
│ │ ├── api/ # API service barrel
│ │ └── storage/ # react-secure-storage wrapper
│ ├── lib/
│ │ ├── config/ # seo, app-apis, app-paths, app-locales, constants, api-url (getApiBaseUrl)
│ │ ├── env/ # Zod-validated env schema (schema.ts, validate.ts)
│ │ ├── logger/ # Pino logger with auto-redaction
│ │ ├── errors/ # ApiException (carries requestId), catchError
│ │ ├── enums/
│ │ └── utils/
│ │ ├── http/ # (opt, http-client)
│ │ │ ├── shared/ # runtime guards, request-id, parseApiError, toSearchParams
│ │ │ ├── axios-client/ # interceptors, token refresh, Result-based
│ │ │ ├── fetch-client/ # same Result pattern on native fetch, typed `params`
│ │ │ ├── __bundle-sentinel__/ # build-time regression gate ('use client' check)
│ │ │ ├── client-utils.ts
│ │ │ ├── token-store.ts # inert in cookie-mode (the default)
│ │ │ ├── index.ts # universal entry — safe in client/server/edge
│ │ │ └── server.ts # server-only entry — forwards next/headers cookies
│ │ ├── ws/ # (opt, ws) socket.io-client wrapper, hooks, Redux bridge
│ │ │ ├── client/ # WsClient + lazy `wsClient` singleton (Proxy)
│ │ │ ├── hooks/ # useWsStatus, useWsEvent, useWsEmit
│ │ │ ├── redux/ # bridge (the only WS dispatcher) + selectors
│ │ │ ├── shared/ # SSR guard, auth carrier, URL composer
│ │ │ ├── types/ # ClientToServerEvents/ServerToClientEvents, payloads
│ │ │ ├── constants.ts # namespace, heartbeat, reconnection bounds
│ │ │ └── index.ts # public barrel
│ │ └── validations/
│ ├── i18n/ # (opt, i18n) routing, request, navigation, translations/
│ ├── styles/globals.css # Tailwind v4 directives
│ └── types/ # common/, utility/ (Result, Either), i18n.ts
├── test/ # (opt, tests)
│ ├── setup.ts # testing-library + jsdom setup
│ └── test-utils.tsx # renderWithProviders, TestProviders
├── scripts/
│ ├── sync-env.ts # .env.example ← .env (respects `-public` markers)
│ └── check-deprecated.ts # fails build if @deprecated APIs are referenced
├── public/
├── biome.json # single-tool lint + format
├── next.config.ts # security headers, reactCompiler (opt), bundleAnalyzer (opt), withNextIntl (opt, i18n)
├── vitest.config.ts # (opt, tests)
├── postcss.config.mjs # @tailwindcss/postcss
├── tsconfig.json
├── .env.example
├── .czrc # (opt, commitizen)
├── .husky/ # (opt, pre-commit hooks)
├── .lintstagedrc.mjs # (opt, pre-commit hooks) runs `biome check --write`
├── commitlint.config.mjs # (opt, pre-commit hooks)
├── Dockerfile # (opt, docker) multi-stage, standalone mode
├── docker-compose.yml # (opt, docker)
├── AGENTS.md # Agent coding rules (referenced from CLAUDE.md)
├── CLAUDE.md
└── package.json
Features that have first-class opt-in/opt-out prompts during init: httpClient, darkMode, redux, ws (opt-in, requires redux), i18n, tests, reactCompiler, bundleAnalyzer, docker, ci, preCommitHooks, commitizen, communityFiles, readme, copyEnv. Each opt-out has a matching setup --<feature> to re-add later. doctor and remove cover the whole installed footprint via manifests.
Tech Stack
CLI: TypeScript, esbuild, Commander.js, Enquirer, Vitest, degit.
Generated apps: Next.js 16+, TypeScript, Tailwind CSS v4, Biome, Pino, Zod, Redux Toolkit, @teispace/next-themes, next-intl, Fetch / Axios HTTP clients (shared foundation), socket.io-client (opt-in via ws prompt), Vitest + RTL, React Compiler.
Recent changes
Behaviour added in v2.1.0 beyond the headline features. None of these change the user-facing CLI surface — they harden parts of the generated app that previously needed manual cleanup.
- HTTP sentinel + server entry auto-align. The template ships
src/lib/utils/http/__bundle-sentinel__/client-bundle-sentinel.tsxandsrc/lib/utils/http/server.tswith imports of both axios and fetch client modules. When you pick a single client (atinitor viasetup --http-client), the inactive variant's directory is removed — and the two files would normally still reference it, breakingyarn build. Both are now rewritten in place to mention only the active client(s). Idempotent: re-runningsetup --http-clientagainst the same configuration is a no-op. remove wsstrips cleanly without manual intervention. The WS manifest's reducer-registration and bridge-mount injections now carryremovePatternfunctions, sonext-maker remove wsdeletes thewsReducerimport, thews: wsReducerentry, the unwrapped-on-purpose JSDoc, and theattachWsBridgeuseEffectblock — without any "manual cleanup" messages. The init-timecleanupWsand theremove wsflow share the same pure helpers, so the byte-level output is identical.useEffectimport auto-pruned. When the WS bridge effect is stripped (init opt-out orremove ws), theuseEffectimport is dropped fromStoreProvider.tsx's React import line if no otheruseEffect(calls remain — avoiding the unused-import lint warning.- Wording-tolerant strip helpers. The
stripBundleSentinel,stripBridgeMount, andstripWsReducerRegistrationhelpers now anchor on stable code tokens (import paths, function names, thews+persistReducerkeywords inside JSDoc) rather than literal comment text. Upstream template comment rewording can't silently break cleanup. doctorreports sentinel mount drift — and only real drift. The HTTP manifest tracks the<HttpClientBundleSentinel />mount as a single code injection with two possible homes (alternativeFiles):[locale]/layout.tsxwhen i18n is installed,src/app/layout.tsxotherwise. Only the layout that actually exists is checked, so a healthy project reports clean; if you delete the mount manually,doctorreports it,doctor --fixre-mounts it, andremove http-clientstrips it.
Known issues
setup --reduxpost-init whenwswas opted out. The template'sStoreProvider.tsxships with the WS bridge mount inline.next-maker setup --reduxcopies the template'sStoreProvider.tsxwholesale into the project — so running it on a project wherewsis not wanted leavesimport { attachWsBridge, wsClient } from '@/lib/utils/ws'in place, andyarn buildfails because that module doesn't exist. Workaround until a fix lands: also runnext-maker setup --wsto install the WS layer, thennext-maker remove wsif you don't actually want it (clean removal handles both layers). Tracked separately; out of scope for v2.1.0.doctor --fixcan't recreatesrc/app/[locale]/. Producing that directory means running the i18n migration, which moves app-router pages. Doing that unattended to a project that has since lost the segment could relocate user code with no way back, so the i18n repair skips it and doctor reports it as STILL DRIFTED with guidance. Every other part of the i18n footprint is repaired.- Repairs that need starter assets need network.
doctor --fixre-copies deleted files (src/i18n/,test/setup.ts,CustomThemeProvider.tsx, …) from the starter repo viadegit. Offline, those repairs fail and are reported as STILL DRIFTED rather than silently skipped. Package-only and code-block-only repairs work offline.
Development
Setup
git clone <repository-url>
cd npm-packages/packages/next-maker
yarn install
Build
yarn build # esbuild bundle + tsc --emitDeclarationOnly
Test
yarn test # one-shot
yarn test:watch
The test suite covers every modifier, generator template, and manifest runner with deterministic string/JSON inputs. Generators that touch the filesystem are tested against os.tmpdir() directories.
Smoke-test the built CLI
node dist/index.js init test-project
cd test-project
# Generators
node ../dist/index.js page dashboard --loading --error
node ../dist/index.js layout dashboard
node ../dist/index.js component sidebar --client
node ../dist/index.js hook debounce
node ../dist/index.js provider auth
node ../dist/index.js slice filters --persist
node ../dist/index.js feature users --store persist --service axios
node ../dist/index.js service users --fetch --crud
node ../dist/index.js env SENTRY_DSN --type url --describe "Sentry"
node ../dist/index.js locale es
node ../dist/index.js test src/hooks/useDebounce.ts --force
# Maintenance
node ../dist/index.js doctor
node ../dist/index.js setup --ws # add WebSocket layer (project must already have --redux)
node ../dist/index.js remove redux --dry-run
Adding a new feature manifest
- Drop a file under
src/manifests/<feature>.manifest.tsexporting aFeatureManifest. - Append it to the
MANIFESTSarray insrc/manifests/index.ts. - (Optional) Add a
setup --<feature>flag if you want a flag-based retrofit.
doctor and remove will pick it up automatically.
License
MIT
Contributing
Contributions are welcome. Please open a PR.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Support
For issues and questions, please visit our GitHub Issues.