Nova is an extensible Next.js development toolkit and CLI for creating, generating, managing, and maintaining production-ready applications.
Run one command, answer a few prompts, and get a fully configured Next.js application with a modern architecture, authentication, internationalization, forms, validation, API infrastructure, testing, UI frameworks, CMS integrations, and optional plugins — instead of starting from an empty App Router project and re-inventing the same infrastructure on every new codebase.
npx @darkalpha/nova my-appOr install Nova globally:
npm install -g @darkalpha/novaThen use the nova command:
nova my-appAlready have a project and just want to bolt on a feature later? Nova can do that too:
cd my-app
nova add prisma redisNot sure what a plugin actually adds before you commit to it? Ask the CLI directly:
nova plugins prisma- Install
- Quick Start
- Why Nova?
- Features
- Creating a Project
- CLI Usage
- Project Maintenance
- Adding Features to an Existing Project (
nova add) - Inspecting Plugins (
nova plugins) - What's Included
- Plugins and Add-ons
- UI Frameworks
- Project Architecture
- Repository Structure
- Generator Internals
- Add-on Architecture
- Example Configurations
- Documentation Generated With Your Project
- Environment Variables
- Deployment
- FAQ
- Troubleshooting
- Development
- Testing the Generator
- Adding a New Plugin
- Roadmap
- Contributing
- Changelog
- Links
- License
Use it directly with npx — no install required:
npx @darkalpha/nova my-appOr install Nova globally:
npm install -g @darkalpha/novaThen use the nova command:
nova my-appRequirements: Node.js >=18.18.0, and one of npm, pnpm, yarn, or bun installed on your machine (you choose which one Nova uses per-project during setup).
# Scaffold a new app
npx @darkalpha/nova my-app
cd my-app
cp .env.example .env
npm install # only needed if you skipped install during setup
npm run devOpen http://localhost:3000 — your app is running with locale-aware routing (/en, /fa), a working auth reference implementation with token rotation, and a typed API layer ready to point at your backend via API_BASE_URL.
You can also run Nova with no arguments for a fully interactive setup:
novaNova will prompt you for the project name, package manager, UI library, and any optional add-ons before generating anything — and will refuse to generate a plugin combination it knows is invalid (see Plugins and Add-ons) before writing a single file.
Later, if you decide you need another feature, run nova add from inside the project instead of regenerating it — see Adding Features to an Existing Project. If you're not sure what a plugin does first, run nova plugins <name> — see Inspecting Plugins.
Most Next.js starters provide a basic application and leave important architectural decisions to the developer: How should auth token refresh work? Where do validation schemas live? How is the API client structured? Should translations be typed? Nova answers these questions up front so you don't have to relitigate them on every new project.
Start with a complete architecture instead of an empty App Router project. Depending on your configuration, Nova can provide:
- Authentication
- Token refresh and rotation
- Internationalization
- Form validation
- API infrastructure
- Database access
- Data fetching
- State management
- Testing
- Observability
- Security
- Docker support
Nova is designed around a plugin and add-on architecture.
Choose only the features your project needs.
For example:
Next.js
+
shadcn/ui
+
Prisma
+
Better Auth
+
Redis
+
Strapi
+
TanStack Query
Or keep your application minimal:
Next.js
+
shadcn/ui
+
TypeScript
And if you didn't pick everything up front, nova add lets you layer plugins onto an existing project whenever the need comes up.
Nova is designed to avoid unnecessary vendor lock-in.
You can choose between multiple technologies for different parts of your application.
Examples include:
- shadcn/ui
- Material UI
- Chakra UI
- Ant Design
- Mantine
- HeroUI
- DaisyUI
- Headless UI
And:
- Prisma or Drizzle (mutually exclusive — pick one)
- Redis
- Strapi
- OpenAPI
- TanStack Query
- Zustand
- React Hook Form
- Zod
Generated projects include documentation explaining the architecture and important design decisions.
Documentation may include:
docs/
├── folder-structure.md
├── authentication.md
├── api-layer.md
├── forms.md
├── adding-a-feature.md
├── nova-add-command.md
└── ...
Major modules also ship their own local README.md (for example src/lib/api/, src/lib/auth/, src/features/auth/, src/i18n/) so the reasoning behind non-obvious decisions travels with the code itself, not just in a top-level doc nobody re-reads six months later.
Everything Nova generates is plain, ordinary Next.js/React/TypeScript code that you own outright the moment it's written to disk. There's no runtime dependency on the nova package inside your generated app, no telemetry, and no CLI daemon watching your project. If you want to rip out a feature, delete the files and the corresponding dependency — that's it.
Nova's own generator (not the apps it produces) is built to fail safely and predictably:
- Plugin selections are validated before any file is written, using declared metadata about what conflicts with what and what requires what.
- Every generation runs as a planned sequence of operations; if something fails partway through, Nova rolls back the partially-created project directory instead of leaving a broken half-generated app on disk.
- Every feature's
package.jsonfootprint (dependencies, devDependencies, scripts) is declared exactly once and consumed by both full generation andnova add, so the two paths can never quietly disagree about what a plugin installs.
See Generator Internals for details.
Nova provides a flexible foundation for building modern Next.js applications.
- Next.js App Router
- React and TypeScript
- Feature-first architecture
- Internationalization with RTL support
- Authentication and token rotation
- Better Auth integration
- React Hook Form and Zod
- Type-safe API infrastructure
- Multiple UI frameworks
- Headless CMS integrations
- Database integrations (Prisma or Drizzle)
- Data fetching and state management
- Testing tools
- Storybook
- Docker and Docker Compose
- PWA support
- Email and local SMTP development
- Rich text editing
- Animations
- Charts and data visualization
- Observability and error tracking
- Security features
- Health and readiness endpoints
- Extensible plugin architecture with declared metadata and validation
- Incremental feature addition to existing projects (
nova add) - Plugin introspection from the CLI (
nova plugins)
When creating a project, Nova guides you through the setup process.
You can configure:
- Project name
- Package manager
- UI framework
- Optional plugins and add-ons
- Dependency installation
- Git initialization
Before any files are written, Nova validates your plugin selection against each plugin's declared constraints (requires/conflicts/supported UI libraries) — for example, Prisma and Drizzle cannot both be enabled, since they'd both try to own DATABASE_URL-backed schema and migrations. If something's incompatible, you'll get a clear error instead of a partially broken project.
After generation:
cd my-appCreate your environment file:
cp .env.example .envInstall dependencies if you skipped installation during setup:
npm installStart the development server:
npm run devYour application is now ready for development.
Nova supports project creation, plugin management, and safe project maintenance.
nova [project-name] [options]
nova add <feature...> [options]
nova plugins [feature]
nova remove <plugin...> [--path <dir>] [--force]
nova init | info | doctor | validate | clean | diff [--path <dir>]
nova status [--path <dir>] [--json]
nova upgrade | repair [--path <dir>]
nova list [search-term]
nova search <term>
-h, --help Show this help message
-v, --version Print the installed version
--path, -p <dir> Target project directory (default: current directory)
--force, -f Overwrite files that already exist instead of skipping them
--json Print structured output (maintenance and discovery commands)
nova my-app
nova
nova add prisma redis
nova add drizzle
nova add tanstack-query --path ./my-app
nova plugins
nova plugins drizzle
nova init --path ./my-app
nova doctor --path ./my-app
nova remove drizzle --path ./my-app
nova search database
nova status --path ./my-app --jsonnova init creates .nova.json, Nova's project manifest. New projects and nova add create it automatically. It records selected plugins and lets remove, validate, doctor, and upgrade operate without guessing from user source files. remove intentionally preserves generated files; it removes only tracked package entries and manifest metadata.
Project names must contain only:
- Lowercase letters
- Numbers
- Dashes
- Underscores
The project name follows:
^[a-z0-9-_]+$
Invalid names (spaces, path separators, uppercase letters, ..) are rejected before any files are written, whether passed as a CLI argument or typed into the interactive prompt.
When CI is set in the environment, Nova automatically skips the "install dependencies now?" confirmation prompt so the process can run non-interactively, and its internal logger switches to a quieter, CI-friendly output level:
CI=true npx @darkalpha/nova my-appYou'll still want to run the package manager's install command afterward as a separate CI step.
Nova tracks its own changes in a small .nova.json manifest. It is created automatically for new projects and after nova add; use nova init once to adopt an existing compatible project. The manifest records the package manager, UI library, and plugins selected through Nova. It does not claim ownership of handwritten application code.
| Command | Purpose |
|---|---|
nova status |
Show project identity and a health summary. |
nova doctor |
Check Node.js, package metadata, required files, lockfile presence, and plugin validation. |
nova validate |
Run plugin validations without writing project files. |
nova info |
Print project, framework, package-manager, UI, and tracked-plugin details. |
nova list --installed |
List only plugins recorded in the target project's manifest. |
nova remove <plugin> |
Remove tracked package entries and manifest metadata; generated files remain intact. |
nova upgrade |
Reconcile tracked plugin dependency declarations with Nova's current manifests. |
nova repair |
Repair deterministic metadata drift and a missing .env.example. |
nova diff |
Report detectable baseline drift, such as missing baseline files or unavailable plugin metadata. |
nova clean [--dry-run] |
Remove generated caches such as .next, .turbo, and Nova cache files. |
Maintenance commands accept --path <dir>. Discovery and maintenance commands also support --json, making them suitable for CI checks:
nova doctor --path ./my-app --json
nova status --path ./my-app --json
nova clean --path ./my-app --dry-run --jsonnova remove is intentionally conservative: it never deletes generated source or configuration files, because they may have been edited after generation. Review those files separately after removing a plugin, then run your package manager's install command.
nova add copies an addon's files into a project that already exists on disk and merges its dependencies/scripts into that project's package.json. It's the incremental counterpart to running nova <name> from scratch — useful when you decide you want Prisma, Drizzle, Redis, Sentry, or any other plugin after the fact, without regenerating the whole app.
nova add <feature...> [options]If you omit the feature list, you get an interactive multiselect with the same options as the initial generator prompt.
| Flag | Description |
|---|---|
--path, -p <dir> |
Target project directory (default: current directory) |
--force, -f |
Overwrite files that already exist instead of skipping them |
# Run from inside the project
cd my-app
nova add prisma redis
# Or target another directory
nova add tanstack-query --path ./my-app
# Re-copy files even if they already exist
nova add sentry --forceFeature names accept either the camelCase key (tanstackQuery) or the kebab-case addon folder name (tanstack-query) — both resolve to the same feature.
- Reads the target's
package.json(the directory must already be a Node/Next.js project —nova addnever creates a project itself). - Detects whether the project keeps its code under
src/or at the project root, and copies each addon's files accordingly — any file the addon authors undersrc/...is copied without thesrc/prefix when the target project has nosrc/directory. - Creates whatever intermediate folders are needed (
lib/redis,lib/db,emails/,.storybook/, etc.) — nothing needs to exist beforehand. - Merges the addon's
dependencies/devDependenciesintopackage.json(a newer pinned version wins), and adds any newscriptsentries without overwriting a script you've already customized. These additions come from a single shared source (src/featureContributions.ts) — the exact same data full generation uses, so a feature installs identically whether you selected it up front or added it later. - Skips files that already exist in the project, so re-running
nova addis safe — pass--forceif you deliberately want the shipped template version back. - Validates the requested selection against declared plugin constraints (
requires/conflicts) before writing anything — e.g.nova add drizzlefails fast with an actionable error if Prisma was already requested in the same command.
Plugin lifecycle hooks and any plugin-declared templates, patches, environment variables, and documentation run as part of nova add. Successful additions are recorded in .nova.json for safe later maintenance.
- Switching UI libraries (
mui,chakra,ant, ...) isn't supported vianova add— that requires rewriting the app's provider tree, which is only handled during initial generation. - Config-file wiring that the generator applies automatically at scaffold time (e.g.
output: "standalone"innext.config.mjsfor Docker, or wrappingnext.config.mjsfor Sentry/PWA/Bundle Analyzer) is not re-applied bynova add. Files are copied and dependencies are added, but you may need to wire a couple of lines intonext.config.mjsby hand — check that feature'sdocs/*.mdin the generated project for the exact snippet. - Conflict detection for
nova addcurrently only checks the features requested in that single command against each other — it does not yet cross-reference plugins already installed in the target project (tracked in.nova.json) before writing files. Runningnova add drizzleagainst a project that already has Prisma installed will not be automatically blocked; checknova info/nova pluginsfor existing plugins before adding a conflicting one. - Run your package manager's install command afterwards;
nova addonly updatespackage.json, it doesn't install anything.
Full reference: docs/nova-add-command.md inside any generated project.
Before adding a plugin — or just to understand what one already in your project does — you can ask the CLI directly instead of reading source files:
nova pluginsLists every available plugin with its description, any requires/conflicts constraints, which UI libraries it's restricted to (if any), and a summary of what it adds to package.json.
nova plugins drizzleShows the same detail for a single plugin.
This command reads from the exact same metadata (src/generator/pluginMetadata.ts) and package contribution data (src/featureContributions.ts) that generation itself uses — there's nothing plugin-specific to keep in sync by hand, and the output can never drift from what nova <name> or nova add actually do.
Every generated project includes a production-oriented foundation.
- Next.js App Router
- React
- TypeScript
- Strict TypeScript configuration
- Tailwind CSS
- shadcn/ui-style primitives
- Optional alternative UI frameworks
- next-intl
- Locale-prefixed routing
- English and Persian locales by default
- RTL-ready architecture
The base architecture supports:
- HTTP-only cookies
- Access and refresh tokens
- Refresh token rotation
- Concurrent refresh request coalescing
Better Auth can also be selected as an alternative authentication solution.
- React Hook Form
- Zod
- Shared validation schemas
- Client and server validation
Generated applications can include typed API clients with:
- Retries
- Timeouts
- Interceptors
- Authentication handling
- Automatic token refresh
- Light mode
- Dark mode
- System theme
- next-themes
Nova's plugin ecosystem is designed to be composable.
The available options depend on the version of Nova you are using. Every plugin listed below can be selected either during initial generation, or added afterward with nova add (see Adding Features to an Existing Project), and inspected at any time with nova plugins (see Inspecting Plugins).
- Prisma ORM
- Drizzle ORM
- Better Auth
- Redis
- Strapi CMS
- OpenAPI typed client
- TanStack Query
- TanStack Table
- Zustand
- MSW API mocking
- Vitest
- Playwright
- Cypress
- Storybook
- React Email
- Mailpit
- Tiptap rich text editor
- Docker
- Docker Compose
- Husky
- lint-staged
- PWA
- Bundle Analyzer
- Sentry
- Health and readiness endpoints
- Security headers
- Design System
- Animations
- Recharts
| Category | Options |
|---|---|
| Data and Backend | Prisma ORM, Drizzle ORM, Better Auth, Redis, Strapi CMS, OpenAPI typed client |
| Data Fetching and State | TanStack Query, TanStack Table, Zustand, MSW API mocking |
| Testing | Vitest, Playwright, Cypress, Storybook |
| Content and Communication | React Email, Mailpit, Tiptap rich text editor |
| Infrastructure and Operations | Docker, Docker Compose, Husky + lint-staged, PWA, Bundle Analyzer, Sentry, Health/readiness endpoints, Security headers |
| Design and UX | Design System, Animations, Recharts |
Each plugin is a self-contained overlay under templates/addons/<name>. Enabling it copies its files on top of the base template and automatically wires in the required dependencies, scripts, and environment variables — there's no manual wiring step after generation. The same addon folders back nova add, so a feature behaves identically whether you selected it at scaffold time or added it later.
Each plugin also carries declarative metadata (src/generator/pluginMetadata.ts) describing its name, description, and any requires/conflicts/supportedUI constraints. Nova validates your full selection against this metadata before writing any files, so an incompatible combination fails fast with a clear error rather than partway through generation.
| Plugin A | Plugin B | Reason |
|---|---|---|
Prisma ORM (prisma) |
Drizzle ORM (drizzle) |
Both own DATABASE_URL-backed schema/migrations and would contribute colliding db:* scripts — pick one ORM per project. |
If you need to evaluate both, generate two separate projects (or use nova add against two throwaway scaffolds) rather than trying to enable both in one.
Nova supports multiple UI approaches.
The default UI approach.
Provides:
- Tailwind CSS
- Radix UI primitives
- Source-based components
- Accessible components
Select:
shadcn
Provides:
- MUI components
- Theme configuration
- App Router integration
- Cache provider
Select:
mui
Provides:
- Chakra provider
- Theme configuration
- Component examples
Select:
chakra
Provides:
- Ant Design components
- ConfigProvider
- Theme support
- Component examples
Select:
ant
Provides:
- Mantine provider
- Theme configuration
- Mantine components
Select:
mantine
Provides:
- HeroUI provider
- Modern component primitives
- Theme integration
Select:
hero
Provides:
- DaisyUI
- Tailwind integration
- Theme configuration
Select:
daisy
Provides:
- Headless UI primitives
- Tailwind integration
- Heroicons
Select:
headless
| Select | Library | Notes |
|---|---|---|
shadcn |
shadcn/ui (default) | Tailwind CSS, Radix UI primitives, source-based, accessible components |
mui |
Material UI | MUI components, theme configuration, App Router cache provider |
chakra |
Chakra UI | Chakra provider, theme configuration, component examples |
ant |
Ant Design | Ant Design components, ConfigProvider, theme support |
mantine |
Mantine | Mantine provider, theme configuration, components |
hero |
HeroUI | HeroUI provider, modern component primitives |
daisy |
DaisyUI | Tailwind plugin, theme presets |
headless |
Headless UI | Headless UI primitives, Tailwind integration, Heroicons |
Note: switching UI library after generation is only supported by re-generating the project — nova add cannot swap the provider tree for you (see Known limitations above).
Generated applications follow a feature-first architecture.
src/
├── app/
│ └── [locale]/
│
├── components/
│ ├── ui/
│ ├── layout/
│ ├── forms/
│ └── providers/
│
├── features/
│ ├── auth/
│ └── dashboard/
│
├── lib/
│ ├── api/
│ ├── auth/
│ ├── prisma/
│ ├── db/
│ ├── validations/
│ ├── cache/
│ └── helpers/
│
├── services/
│
├── utils/
│
├── i18n/
│
└── messages/
├── en/
└── fa/
For reference, the complete generated src/ tree (with every optional folder included) looks like this:
src/
├── app/ # App Router routes, grouped under [locale]
├── actions/ # Cross-cutting Server Actions not tied to one feature
├── components/
│ ├── ui/ # shadcn/ui-style primitives (button, input, card...)
│ ├── common/ # Small generic UI (spinner, empty-state)
│ ├── layout/ # Header, footer, theme/locale switchers
│ ├── forms/ # Form wrapper, field, error components
│ └── providers/ # Client-side provider wrappers (theme, etc)
├── features/ # Feature-first modules (auth, dashboard, profile...)
│ └── <feature>/
│ ├── components/
│ ├── hooks/
│ ├── actions/
│ ├── schemas/
│ └── types/
├── hooks/ # App-wide reusable hooks
├── lib/
│ ├── api/ # Type-safe fetch client(s) + interceptors
│ ├── auth/ # Token rotation, session helpers
│ ├── prisma/ # Prisma client singleton (if enabled)
│ ├── db/ # Drizzle client + schema (if enabled instead of Prisma)
│ ├── validations/ # Shared zod schemas
│ ├── constants/ # App-wide constants
│ ├── helpers/ # cn() and other small helpers
│ └── cache/ # Cache tag registry
├── services/ # Business-shaped API calls, built on lib/api
├── utils/ # Pure utility functions (formatting, arrays, strings)
├── config/ # Site config, validated env
├── providers/ # Composition root for all client providers
├── styles/ # Global CSS
├── types/ # Shared TypeScript types
├── messages/ # next-intl translation JSON per locale
└── i18n/ # next-intl routing/navigation/request config
Note: if a project doesn't use a src/ directory (e.g. create-next-app with the "Use src/ directory?" prompt declined), nova add detects this automatically and remaps addon files to the project root instead — see Adding Features to an Existing Project.
Features
Own everything specific to a product area.
features/
└── users/
├── components/
├── actions/
├── schemas/
└── types/
Lib
Contains cross-cutting infrastructure.
Examples:
- API
- Authentication
- Database
- Caching
- Validation
Components
Contains reusable UI without business-specific logic.
Services
Contains business-oriented operations built on top of the API layer.
Features should call services rather than directly calling the API transport layer.
This keeps business logic independent from the underlying HTTP implementation.
Rule of thumb
features/owns anything specific to one product area — start here for new work.lib/owns cross-cutting infrastructure (HTTP, auth, DB).components/owns UI with no business logic.services/is the only place allowed to calllib/apidirectly for business data; features call services, notapi, so the transport can change independently.
The Nova repository itself is organized as follows:
.
├── bin/
│ └── nova.js
│
├── src/
│ ├── index.ts
│ ├── add.ts
│ ├── prompts.ts
│ ├── generator.ts
│ ├── generator/
│ │ ├── context.ts
│ │ ├── logger.ts
│ │ ├── errors.ts
│ │ ├── hooks.ts
│ │ ├── operations.ts
│ │ ├── pluginMetadata.ts
│ │ ├── pluginInfo.ts
│ │ ├── validators.ts
│ │ ├── verifyManifestSync.ts
│ │ └── patchers/
│ │ ├── types.ts
│ │ ├── nextConfigPatcher.ts
│ │ ├── providerPatcher.ts
│ │ ├── middlewarePatcher.ts
│ │ └── index.ts
│ ├── plugin/
│ │ ├── types.ts
│ │ ├── registry.ts
│ │ ├── legacyAdapter.ts
│ │ ├── dependencyGraph.ts
│ │ ├── applyTemplates.ts
│ │ ├── applyPatches.ts
│ │ ├── applyEnv.ts
│ │ ├── applyDocs.ts
│ │ ├── runHooks.ts
│ │ ├── validate.ts
│ │ ├── prompts.ts
│ │ └── nativePlugins/
│ │ ├── prisma.ts
│ │ ├── drizzle.ts
│ │ ├── dockerCompose.ts
│ │ └── securityHeaders.ts
│ ├── addonRegistry.ts
│ ├── featureContributions.ts
│ ├── featurePackageAdditions.ts
│ ├── packageMerge.ts
│ ├── projectStructure.ts
│ ├── packageManifest.ts
│ └── types.ts
│
├── packages/
│ └── core/
│ └── src/
│ ├── fs.ts
│ ├── pmCommands.ts
│ ├── prompts.ts
│ └── logger.ts
│
├── templates/
│ ├── base/
│ ├── addons/
│ └── ui/
│
├── scripts/
│ ├── smoke-test.mjs
│ └── verify-package-manifest-sync.ts
│
└── docs/
└── migration/
bin/nova.js— the published CLI entrypoint (used bynpm startornpx).src/index.ts— the CLI entrypoint; dispatches between thenova [project-name]generation flow,nova add <feature...>, andnova plugins [feature].src/generator/index.ts— high-level generation orchestration: validates the plugin selection, builds an operation plan (copy base template, copy selected addons, copy UI overlay), executes it with rollback on failure, writespackage.json, and runs the config patchers and remaining single-purpose helpers (Storybook preview, DaisyUI Tailwind patch, README, lint-staged, docker-compose).src/generator/— generator internals, one concern per module (see Generator Internals below).src/plugin/— the native plugin engine (PluginManifest, registry, dependency graph, template/patch/env/doc application, lifecycle hooks). New plugins with richer contributions (self-declared env vars, docs, patches, prompts) are authored here undernativePlugins/, e.g.drizzle.ts.src/add.ts— thenova addimplementation: copies addon files into an existing project (withsrc/-prefix remapping) and merges dependencies/scripts into itspackage.json, running the same plugin engine (dependency graph, validation, templates, patches, env, docs) full generation uses.src/addonRegistry.ts— single source of truth mapping eachFeatureKeyto its addon folder name, shared bygenerator/index.ts,add.ts, andpluginInfo.ts.src/featureContributions.ts— single source of truth for what each feature contributes topackage.json(dependencies/devDependencies/scripts), consumed by full generation,nova add, andnova plugins.src/featurePackageAdditions.ts— thin backward-compatible re-export offeatureContributions.tsfor any code still importing the old name.src/packageMerge.ts— merges an addon's dependency/script additions into an existing project'spackage.jsonwithout clobbering scripts you've already customized.src/projectStructure.ts— detects whether a target project uses asrc/directory and remaps addon file paths accordingly fornova add.src/prompts.ts— the interactive@clack/prompts-based CLI flow, shared between initial generation and thenova addfeature multiselect.src/packageManifest.ts— builds a brand-new generated project'spackage.json(scripts + dependencies) from the selected feature set andfeatureContributions.ts.packages/core/—@nova/core, a framework-agnostic workspace package with zero feature-specific logic: template/filesystem copying, package-manager command resolution, prompt cancel-handling, and logging. Built once and bundled into the CLI at publish time.templates/base/— the complete base Next.js App Router project that every generated app starts from.templates/addons/— one folder per optional feature; each overlays files on top oftemplates/basewhen enabled, and is reused verbatim bynova add.templates/ui/— one folder per alternative UI library, overlaid last so provider wiring and examples land correctly.scripts/verify-package-manifest-sync.ts— regression guard confirmingpackageManifest.ts's output for every feature matchesfeatureContributions.ts; runs in CI andprepublishOnly.
Nova's own generator (as distinct from the apps it produces) went through a Phase 1 hardening pass focused on making it robust, predictable, and easy to extend as more plugins are added. The pieces:
| Module | Responsibility |
|---|---|
src/generator/context.ts |
Builds a single, frozen GeneratorContext (paths, resolved UI library, logger, dry-run flag) threaded through generation instead of recomputing values or relying on mutable module state. |
src/generator/logger.ts |
A small structured logger (debug/verbose/info/success/warn/error/step) with a CI-aware minimum log level, replacing scattered console.log calls. |
src/generator/errors.ts |
Typed error classes (InvalidProjectNameError, DirectoryNotEmptyError, PluginConflictError, MissingPluginDependencyError, OperationExecutionError, ...) so failures are identifiable and carry actionable messages instead of generic Errors. |
src/generator/pluginMetadata.ts |
Declarative per-plugin metadata: name, description, requires, conflicts, supportedUI. The single place new cross-plugin constraints get declared. |
src/generator/validators.ts |
validatePluginSelection() checks a feature selection against pluginMetadata.ts before any files are written. |
src/generator/pluginInfo.ts |
Joins addonRegistry.ts, pluginMetadata.ts, and featureContributions.ts into one queryable view per plugin; powers nova plugins. |
src/generator/operations.ts |
Represents file operations (mkdir, copyDir, writeFile, writeJson) as plain data (an OperationPlan) rather than closures, executed sequentially by executePlan(). On failure, rollbackTargetDir() removes the partially-generated project directory instead of leaving it half-built. |
src/generator/hooks.ts |
A minimal HookRegistry supporting beforeGenerate / afterGenerate / beforePlugin / afterPlugin lifecycle hooks, so future commands can observe generation without editing generator internals. |
src/generator/patchers/*.ts |
Config patching for next.config.mjs, the provider tree (app-providers.tsx), and middleware.ts, expressed as ordered, declarative contribution lists (feature flag → transform) instead of inline if chains in generator.ts. |
src/generator/verifyManifestSync.ts |
Regression guard confirming buildPackageJson()'s output matches featureContributions.ts per feature — exercised by npm run verify:manifest-sync. |
src/plugin/* |
The richer, self-describing plugin engine: PluginManifest (templates/patches/env/docs/prompts/hooks), PluginRegistry, resolveDependencyGraph() (requires/conflicts/cycles), and applier modules invoked by both full generation and nova add. New plugins with non-trivial contributions (like Drizzle's env entry) are authored here. |
Design principles carried through all of the above:
- Single source of truth per concern. A feature's package.json footprint lives in exactly one place (
featureContributions.ts); a plugin's cross-plugin constraints live in exactly one place (pluginMetadata.ts); config patches live in exactly one place per target file (patchers/*.ts). - Validate before writing. Plugin selection is checked against declared constraints before the target directory is even created.
- Plan, then execute. File operations are built as a data structure first and executed second, which is what makes rollback-on-failure possible and will make a future
--dry-runflag a small addition rather than a rewrite. - No behavior change for existing users. Every refactor in this pass preserves byte-identical generated output for every existing feature/UI combination — verified against
scripts/smoke-test.mjs.
This foundation is what a future nova doctor, nova upgrade, or nova remove command would build on: pluginInfo.ts already answers "what does this plugin touch," pluginMetadata.ts already answers "what does this plugin require or conflict with," and the hook registry already gives a place to add cross-cutting behavior without touching generator.ts again.
Nova uses an overlay-based add-on architecture.
Each add-on lives under:
templates/addons/<name>
When an add-on is enabled, its files are applied on top of the base template.
For example:
templates/
├── base/
│
└── addons/
├── prisma/
├── drizzle/
├── redis/
├── strapi/
├── sentry/
└── playwright/
This allows features to be composed without requiring complex template generation logic.
Add-on files can intentionally override base files when required.
Examples include:
middleware.tsnext.config.mjsapp-providers.tsx
The goal is to keep each plugin isolated, explicit, and easy to maintain. There is no deep merge logic for file overlays — an overlay file simply replaces the base file at the same path, so it's always obvious, by reading the addon folder, exactly what it changes. (Config patching — as opposed to file overlaying — is handled separately and declaratively; see Generator Internals.)
UI library overlays (templates/ui/*) are applied last, after all selected addons, so provider wiring (e.g. wrapping <AppProviders> with <MuiProvider> or <ChakraAppProvider>) is consistent regardless of which other addons were selected.
The exact same templates/addons/<name> folders power nova add: when adding a feature to an existing project, Nova copies the same files, only remapping the src/ prefix if the target project doesn't use a src/ directory. The one thing nova add does not replay is the config-patching step (next.config.mjs, middleware.ts, provider-tree wiring) that generateProject performs automatically via src/generator/patchers/*.ts for legacy contributions — plugins migrated to the native src/plugin/ engine (like Drizzle's env contribution, or Security Headers' patches) do replay through nova add, since applyPluginPatches/appendPluginEnvContributions/writePluginDocs/applyPluginTemplates are shared by both generateProject and addFeaturesToProject.
Minimal marketing or landing site
UI: shadcn
Plugins: (none)
SaaS app with a Postgres backend (Prisma)
UI: shadcn
Plugins: Prisma, Better Auth, TanStack Query, Sentry, Docker
SaaS app with a Postgres backend (Drizzle)
UI: shadcn
Plugins: Drizzle, Better Auth, TanStack Query, Sentry, Docker
Content-driven site backed by a headless CMS
UI: mantine
Plugins: Strapi CMS, React Email, Mailpit
Internal dashboard or admin tool
UI: mui
Plugins: TanStack Query, TanStack Table, Recharts, Zustand
Fully-loaded reference build (mirrors the CLI's own smoke test)
UI: mui
Plugins: Prisma, Better Auth, TanStack Query, Cypress, Vitest, Storybook,
Docker, Husky, PWA, Bundle Analyzer, Zustand, MSW, React Email,
Playwright, Sentry, OpenAPI
Started minimal, grew into a SaaS app (using nova add)
nova my-app # UI: shadcn, no plugins
cd my-app
nova add drizzle betterAuth # add DB + auth later
nova add tanstackQuery sentry # add data fetching + monitoring laterNot sure what a plugin does before adding it
nova plugins prisma
nova plugins drizzle
nova add prisma sentryEvery scaffolded app ships with its own docs/ folder so the architecture is explained in place, not just in this README:
docs/
├── folder-structure.md
├── authentication.md
├── api-layer.md
├── forms.md
├── validation.md
├── server-actions.md
├── internationalization.md
├── environment-variables.md
├── deployment.md
├── adding-a-feature.md
├── nova-add-command.md
├── drizzle.md (if Drizzle is selected)
└── ...
Plus module-local README.md files inside src/lib/api/, src/lib/auth/, src/lib/db/ (if Drizzle is selected), src/features/auth/, and src/i18n/ explaining the reasoning behind non-obvious implementation choices — for example why token refresh coalesces concurrent requests, or why the root layout.tsx is intentionally minimal.
docs/nova-add-command.md specifically documents the nova add workflow from inside a generated project — usage, options, what it does under the hood, and its known limitations — so the reasoning travels with the code even if this README changes later.
Generated projects include a .env.example covering every variable the scaffold understands, grouped by category:
- App — public URL/name, safe to expose via
NEXT_PUBLIC_* - Database —
DATABASE_URL, used by Prisma or Drizzle, whichever is enabled - Authentication — token secrets/TTLs for the custom rotation system, or Better Auth secrets if that module is enabled
- External APIs — base URL/timeout for
src/lib/api - Sentry — DSNs and trace sample rates, when Sentry is enabled
.env itself is never committed — only .env.example is tracked in generated projects. When you add a new environment variable, add it to both .env and .env.example together, and to src/config/env.ts if the app should fail fast at boot when it's missing.
When a plugin is added later via nova add, remember to also copy over any new variables its docs/*.md mentions — for plugins on the native src/plugin/ engine (e.g. Drizzle), nova add appends any missing keys to .env.example automatically via appendPluginEnvContributions; for legacy addons this may still require a manual copy.
Vercel (recommended default) — push to a connected git repository; Vercel auto-detects Next.js. Set the environment variables from .env.example in the Vercel project settings.
Docker (if selected during generation)
docker build -t app .
docker run -p 3000:3000 --env-file .env appThe generated Dockerfile uses a multi-stage build (deps → build → runtime) and Next.js output: "standalone" for a minimal production image.
Every variable in .env.example must be set in production — missing required variables fail fast at boot if you're using the generated src/config/env.ts validation.
Provider-specific deployment tooling (Vercel/AWS/Cloudflare/Railway/Render config generators, nova deploy) is planned but not yet implemented — see Roadmap.
Does Nova lock me into a specific backend?
No. The generated API layer (src/lib/api) is a thin, typed fetch wrapper you point at any backend via API_BASE_URL. Nothing in the generated code assumes a specific server framework or database.
Should I pick Prisma or Drizzle?
Both are optional and mutually exclusive — Nova will reject a selection with both enabled. Pick Prisma if you want Prisma Studio, its generated client, and its larger example ecosystem; pick Drizzle if you want a smaller runtime, SQL-first schema definitions, and no separate client-generation step. See nova plugins drizzle and nova plugins prisma, or each plugin's docs/*.md in a generated project, for the full tradeoff writeup.
Can I remove a feature after generating the project?
Yes. Everything Nova writes is plain, readable TypeScript/React you own outright — delete the files and the corresponding dependency entries in package.json. There's no hidden runtime tying the app back to the nova CLI package. nova remove <plugin> automates the package.json/manifest side of this.
Can I add a feature I skipped, without regenerating the whole project?
Yes — that's exactly what nova add <feature...> is for. Run it from inside the project (or point it at another directory with --path), and it copies the addon's files and merges its dependencies/scripts into your existing package.json. See Adding Features to an Existing Project.
How do I know what a plugin will actually add before I run it?
Run nova plugins <feature>. It shows the plugin's description, any requires/conflicts constraints, supported UI libraries, and a summary of its package.json footprint — all sourced from the same data generation itself uses.
Does nova add overwrite files I've already customized?
No, by default it skips any file that already exists at the destination. Pass --force if you deliberately want the shipped template version back, overwriting your local changes.
What happens if generation fails partway through?
Nova rolls back: since it only ever generates into a directory it just created (or confirmed was empty), a failed generation removes that directory rather than leaving a half-built project behind. This is also what happens if you request an invalid combination like prisma + drizzle — the error is raised before any file is written, so nothing is left on disk.
Does Nova modify my project after generation, or phone home?
No. Nova runs once, at generation time (or once per nova add invocation), entirely locally. There's no CLI daemon, no telemetry, and no ongoing dependency on nova/@darkalpha/nova inside the generated app.
Can I use Nova in CI?
Yes — pass a project name as a CLI argument and set CI=true in the environment; Nova will skip the interactive "install now?" confirmation, use a quieter log level, and generate non-interactively.
Which package managers are supported?
pnpm, npm, yarn, and bun. Whichever you choose during setup is used consistently for the generated scripts and documented install/dev commands in the project's own README.md — including plugin-contributed scripts like Drizzle's db:generate/db:migrate/db:push/db:studio, which call drizzle-kit directly rather than assuming npm.
What if I select a UI library other than shadcn — do I still get shadcn's primitives?
The templates/ui/<library> overlay is applied after the base template and after your selected addons, wiring in that library's provider and a couple of example components. shadcn-style primitives in src/components/ui remain in the tree unless you remove them; feel free to delete what you don't use.
Can I switch UI libraries with nova add?
No — switching UI libraries requires rewriting the provider tree, which is only handled during initial generation. nova add will warn you if you pass a UI library name as a feature.
Can I add a plugin that isn't listed?
Yes — see Adding a New Plugin. Plugins are just overlay folders under templates/addons/<name> plus a feature flag, a metadata entry, and a package.json contribution, so adding one to a fork of Nova is a small, mechanical change, and it automatically becomes available to the initial generator, nova add, and nova plugins.
Directory "my-app" already exists and is not empty.
Nova refuses to generate into a non-empty directory to avoid clobbering existing files. Pick a different project name or empty the target directory first.
No package.json found in "<dir>" when running nova add.
nova add only works inside an existing Node/Next.js project — it never scaffolds one. Run it from the project root, or pass the correct directory with --path.
nova add says a file was skipped.
That means the destination file already existed. This is intentional so your customizations aren't silently overwritten — pass --force if you want the addon's version instead.
nova add warns "looks like a UI library, not a feature."
UI library switching (mui, chakra, ant, mantine, hero, daisy, headless) isn't supported incrementally — see Known limitations.
Generation fails with a plugin conflict or missing-dependency error (e.g. Prisma + Drizzle).
Nova validates your plugin selection against declared metadata before writing anything. Run nova plugins <feature> for each plugin involved to see its requires/conflicts list, then adjust your selection to include only one of the conflicting plugins.
TypeScript can't resolve @nova/core while developing Nova itself.
Run npm install at the repository root first — @nova/core is a real workspace package, and both the editor's TypeScript server and tsc --noEmit need the workspace symlink (or the paths mapping in tsconfig.json) to resolve it.
Git init or dependency install fails after generation.
Nova continues even if git init/git commit or the install step fails, and prints a warning with the exact command to run manually (e.g. pnpm install) inside the generated project directory.
Clone the repository and install dependencies:
npm installRun the CLI in development mode:
npm run devBuild the project:
npm run buildRun type checking:
npm run typecheckVerify the package manifest data hasn't drifted:
npm run verify:manifest-syncRun the compiled CLI:
npm startIf the project uses the @nova/core workspace package, run npm install from the repository root before running development, build, or type-check commands.
Nova includes end-to-end smoke tests for generated projects, plus a dedicated regression guard for package manifest data.
Run the smoke tests:
node scripts/smoke-test.mjsThe smoke tests generate multiple project configurations and verify that the expected files are created.
Test scenarios may include:
- Default configuration
- Full feature configuration
- Different UI frameworks
- Infrastructure plugins
- Redis
- Mailpit
- Health checks
- Security headers
- Docker Compose
- Drizzle ORM (files, scripts, dependencies,
.nova.jsontracking) - The Prisma/Drizzle conflict, asserting generation fails and leaves no partial project directory on disk
Run the manifest sync check:
npm run verify:manifest-syncThis confirms buildPackageJson()'s output for every feature matches src/featureContributions.ts — the single source of truth both full generation and nova add read from. Since both paths consume the same data, this should always pass; it exists as a regression guard against a future change accidentally bypassing that shared source.
When adding or modifying a plugin, update the relevant smoke tests. If your change affects nova add behavior specifically (e.g. new package additions in src/featureContributions.ts), verify it manually against a scaffolded project until incremental-add coverage is added to the smoke suite.
To add a new plugin:
- Create the plugin template under:
templates/addons/<plugin-name>
- Add the corresponding feature key to:
src/types.ts
- Register the plugin's addon folder in:
src/addonRegistry.ts
- Add the plugin's dependencies and scripts once, in:
src/featureContributions.ts
Both src/packageManifest.ts (full generation) and nova add read from this file automatically — there is nothing further to duplicate.
- Add descriptive metadata, and any real
requires/conflicts/supportedUIconstraints, in:
src/generator/pluginMetadata.ts
This also makes the plugin show up correctly in nova plugins.
- If the plugin needs richer contributions — its own
envvariables,docs, configpatches, orprompts— author it as a native manifest in:
src/plugin/nativePlugins/<plugin-name>.ts
and register it in src/plugin/nativePlugins/index.ts's NATIVE_PLUGINS array (see drizzle.ts for a minimal example built on top of steps 2–5). Plugins that only need a file overlay + package.json contribution don't need this step — they're picked up automatically via the legacy adapter.
- If the plugin needs to patch
next.config.mjs, the provider tree, ormiddleware.tsand hasn't migrated to a native manifest, add a contribution to the relevant file in:
src/generator/patchers/
rather than adding a new if branch to generator/index.ts.
-
Add plugin-specific environment variables (to
.env.examplein the relevant template if unconditional, or via a native manifest'senvarray if plugin-specific, and document them). -
Add documentation (a
docs/<plugin>.mdin the plugin's own addon folder, plus a mention in this README's plugin tables). -
Add smoke tests in
scripts/smoke-test.mjs. -
Run
npm run verify:manifest-syncto confirm the plugin's package.json contribution is consistent. -
Verify that the plugin works independently, both via
nova <name>and vianova add <plugin-name>against an existing project, and checknova plugins <plugin-name>renders sensibly. -
Verify compatibility with related plugins.
-
Run the complete test suite (
npm run typecheck && npm run verify:manifest-sync && node scripts/smoke-test.mjs).
Because a feature's package.json contribution now lives in exactly one file (src/featureContributions.ts), it's no longer possible for full generation and nova add to silently drift apart for a given plugin — npm run verify:manifest-sync exists specifically to catch a regression here.
The following are planned but not yet implemented — they are not selectable today via nova, nova add, or any prompt, and no code in this repository claims otherwise:
- tRPC — type-safe router/procedures integration with the App Router, evaluated alongside (not replacing) the existing
src/lib/apilayer. - GraphQL — a client/server integration appropriate for Next.js, with schema organization and typed codegen.
- Supabase — client/server SDK integration, with explicit compatibility rules against Better Auth, Prisma, and Drizzle.
- React Native templates — a mobile project template, likely requiring a
ProjectTemplate/ProjectTypeconcept distinct from the current Next.js-only generator so future non-Next.js targets don't turn the CLI into framework-specific conditionals. - Cloud deployment providers — a
deployment/<provider>architecture (starting with a small number of providers) generating deployment configuration; not an authenticated, live "click to deploy" integration unless and until explicitly documented as such.
Each will land as its own self-contained plugin (or, for React Native, a new template concept) following the same process as Adding a New Plugin and the Drizzle ORM plugin above, one at a time, with its own tests and documentation before the next begins.
Contributions are welcome.
Before submitting changes:
npm install
npm run typecheck
npm run verify:manifest-sync
npm run build
node scripts/smoke-test.mjsWhen contributing a new plugin or UI integration:
- Keep the plugin isolated.
- Avoid unnecessary dependencies.
- Follow the existing architecture (see Generator Internals).
- Declare package.json contributions once, in
src/featureContributions.ts. - Declare plugin metadata and any real constraints in
src/generator/pluginMetadata.ts. - Prefer a native plugin manifest (
src/plugin/nativePlugins/) over a newifbranch ingenerator/index.ts. - Update documentation.
- Add tests.
- Add smoke-test coverage.
- Document plugin conflicts and dependencies.
- Keep generated applications production-ready.
- Run
npm run verify:manifest-syncbefore opening a PR that touches package.json contributions.
Prefer small, focused changes.
See CHANGELOG.md for a full history of changes to Nova itself (not the projects it generates).
MIT