@teispace/next-maker
v3.0.6
Published
Create a new Teispace Next.js application with interactive setup.
Maintainers
Readme
@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-scriptsEvery 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 | Re-runs apply() for every drifted feature (idempotent — safe to retry) |
| --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.
# 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.jsonSample 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.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 --yesSample 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: tsxCode 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 --errorlayout — 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/settingsRefuses 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.tsAuto-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/authfeature — 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.tsnpx @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/modulesslice — 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/storeservice — 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/servicesprovider — 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 workThe 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 3000The 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-translationshook — 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/authtest — 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 --forceRequires 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-runOutputs 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 devFeature-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 --errorMaintenance — 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 i18nArchitecture
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 handlersThe 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
presenceand optionalremovePattern) - apply — the existing
setup<X>()function (forsetupanddoctor --fix) - 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.jsonFeatures 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. The HTTP manifest tracks the<HttpClientBundleSentinel />mount as a code injection in either[locale]/layout.tsxorsrc/app/layout.tsx. If you delete the mount manually,doctorreports it;remove 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 --fixis interactive. Some setup services (notablysetup --http-client) prompt for an action when the feature is partially installed.doctor --fixinvokesapply()directly, so the prompt fires during what's meant to be a non-interactive repair. Workaround: run the specificsetup --<feature>manually and pick the appropriate action. A non-interactive repair path is on the roadmap.
Development
Setup
git clone <repository-url>
cd npm-packages/packages/next-maker
yarn installBuild
yarn build # esbuild bundle + tsc --emitDeclarationOnlyTest
yarn test # one-shot
yarn test:watchThe 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-runAdding 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.
