Apps as Templates
Use renoun apps as living templates that you extend through overrides—a modern alternative to traditional scaffolding.
A renoun app is a complete, runnable application that lives in node_modules and serves as a
template you can extend. Think of apps like @renoun/blog, @renoun/docs, or a Presenter-style
app as bases that you customize by adding your own files.
This “Apps as Templates” approach differs from traditional scaffolding:
| Traditional Templates | Apps as Templates |
|---|---|
| Scaffold once → own all the code | Depend on the app → override what you need |
| Hard to update | Upgradeable via npm |
| You maintain everything | You only own your customizations |
When you run renoun dev or renoun build:
- The renoun app is the base .
- Your files override the app’s files additively.
-
renoun builds a runtime under
.renoun/, merges both, and launches the framework there.
If you already have your own Next/Vite/Waku app and just want renoun inside it, use
”Use renoun in your app” (renoun next dev,renoun vite dev, etc.) instead.
How it works
The “Apps as Templates” system works like Docker’s image layers—you have a base image (the app)
and your overrides on top. When you run renoun dev, the CLI merges them:
Your Project (your overrides)
├── package.json
├── posts/ ← your content
│ └── hello-world.mdx
└── .renoun/app/-renoun-blog/ ← merged runtime
├── ... ← base (from the app)
├── posts/ ← your overrides (linked to your posts/)
└── node_modules/ ← shared dependencies
-
Runtime preparation
The CLI creates
.renoun/app/<app-name>and copies the base app’s files into it. Onlynode_modulesis symlinked to avoid duplication. - Override application Your files are applied on top using an additive model—files you create are linked into the runtime, merging with the base app rather than replacing it entirely.
-
Framework launch
The CLI spawns the framework (
next dev,vite dev,waku dev, etc.) inside the runtime directory . The framework sees the merged result: base app + your customizations. - Path resolution renoun sets an internal environment variable so its utilities resolve paths relative to the runtime directory, ensuring your overridden content is discovered correctly.
This architecture means you never modify the template in node_modules; you only add and edit
files in your own project. When the base app updates, you get the improvements automatically.
Quick start
-
Install the renoun runtime and a template app:
pnpm add renoun @renoun/blog -
Add scripts that call into the renoun CLI:
{ "scripts": { "dev": "renoun dev", "build": "renoun build" } } -
Create your content—files that override parts of the template:
posts/hello-world.mdx -
Run your customized app:
Seepnpm run devexamples/blogfor a complete example that extends the@renoun/blogtemplate with custom posts.
The CLI merges the template into .renoun/app/<package-name>, applies your overrides on top, and
launches the framework (next dev, vite dev, or waku dev). You see the combined result—your
customizations on top of the base app.
Overriding files
Every file or directory that you place next to your package.json—excluding lockfiles and
node_modules—becomes an override. During startup renoun:
-
Copies template files
into the runtime directory. All files are copied so bundlers resolve
imports correctly. Only
node_modulesis symlinked to avoid duplication. - Applies your overrides using an additive model that intelligently merges your files with the template’s files. Your content is added to the base, not replacing everything.
- Enables hot reload because the framework watches the linked files, so edits to your source files trigger rebuilds immediately.
Additive overrides
The override system uses an additive model rather than a replacement model. This means:
- Your files are merged with the template’s files, not replacing them entirely
- You can override specific files while keeping the rest of the app intact
- File metadata is preserved (modification times, etc.) since hard links reference the original files
- No disk space duplication since hard links point to the same data on disk
For example, if you create posts/my-post.mdx and the template already has posts/default.mdx:
Your Project Runtime Directory
├── posts/ ├── posts/
│ └── my-post.mdx │ ├── default.mdx ← from template
└── ... │ └── my-post.mdx ← hard link to yours
└── ...
Both files are visible in the runtime—your content is added alongside the template’s defaults. This additive approach lets you extend templates without losing their default content.
Overriding files
To replace a component shipped with the template, add a file with the same relative path:
// ui/RootProvider.tsx
import {
RootProvider as BaseRootProvider,
type RootProviderProps,
} from 'renoun'
export function RootProvider(props: RootProviderProps) {
return <BaseRootProvider {...props} theme="nord" />
}
The runtime link points to your implementation, so the framework reloads with your changes without extra configuration. Your file takes precedence over the template’s version at that path.
Ejecting from a template
Using a template from node_modules is great for exploring and making light customizations.
When you’re ready to fully own the source, you can eject.
Ejecting turns:
“Extend a template from
node_modules” into “Use renoun in your app.”
What renoun eject does
When you run:
renoun eject
renoun will:
-
Materialize the runtime into your repo
-
Take the current runtime at
.renoun/app/<app-name>. -
Copy all files (e.g.
app/,components/,ui/, configs) into your project root (or a configured target directory). - Preserve paths so the layout matches what you were already running.
-
Take the current runtime at
-
Apply your overrides on top
-
Any files or directories you were overriding (like
posts/orui/RootProvider.tsx) are treated as the source of truth. - If a file exists both in the template and your project, the local file wins .
- Overridden files fully replace the template’s version.
-
Any files or directories you were overriding (like
-
Remove the template dependency
- The ejected app no longer depends on the original template package at runtime.
-
renoun ejectremoves the specified app from yourdependencies/devDependencies, leavingrenounitself in place.
-
Clean up the runtime directory
-
The
.renoun/directory is automatically deleted since you no longer need the runtime environment.
-
The
-
Prepare you to run it as your own app
After ejecting, your app lives directly in your repo. You can switch to running it like any
other framework app with renoun:
(Swap{ "scripts": { "dev": "renoun next dev", "build": "renoun next build" } }nextforvite/wakudepending on the framework the app uses.)
Now you’re using the “Use renoun in your app” flow, with the ejected codebase as your app.
When to eject
Stay in the “Apps as Templates” flow (renoun dev) when:
- You want to try a blog/docs/Presenter app quickly.
- You’re happy with the overall structure and only need targeted customizations.
- You want to keep upgrading the underlying template over time.
Use renoun eject when:
- You’re turning the app into a long-lived product with heavy customizations.
- You want full control over routing, layout, configuration, and dependencies.
-
You’re ready to treat it as your own app and run it via
renoun next dev/renoun vite dev/renoun waku dev.
Building your own app template
Creating an app template is straightforward—it’s just a normal application with a few conventions. Here’s how to build one that works well with renoun’s override system.
Basic structure
An app template is a publishable npm package containing a complete, runnable application:
blog-template/
├── app/ # Your framework routes
│ ├── layout.tsx
│ └── page.tsx
├── posts/ # Content directory for user content
│ └── .gitkeep # Preserves empty directory in git
├── collections.ts # renoun collections
├── package.json # Package with framework deps
└── README.md
Package setup
Your package.json should include the framework and renoun as dependencies:
{
"name": "@your-org/my-template",
"version": "1.0.0",
"dependencies": {
"renoun": "^7.0.0",
"next": "^15.0.0",
"react": "^19.0.0"
}
}
Users install your template alongside renoun, then run renoun dev to use it as their base.
Design for overrides
When building a template, think about what users will want to customize:
-
Content directories
(like
posts/,docs/) — Users add their content here - UI components — Provide sensible defaults that users can override
- Configuration — Keep config in discoverable locations
User files are merged additively with your template, so your defaults show up alongside user content. Design your template to work well with this model.
Gotchas
A few things to watch out for when building templates:
Empty directories aren’t tracked by git. If your template includes empty directories that
users are expected to add content to (like posts/), add a .gitkeep file:
touch posts/.gitkeep
Without this, the directory won’t exist when users install your template from npm. Hidden files
like .gitkeep are automatically excluded from directory listings, so they won’t appear as
content entries.
Build artifacts should be ignored. Add .renoun/, .next/, out/, and node_modules/ to
your .gitignore. The eject command automatically skips these directories.
Test the eject flow. Run renoun eject on your own template to verify the experience is
smooth. Users who outgrow the template model will appreciate a clean transition.
Example
See the @renoun/blog source for
a complete example of a well-structured app template.
Cleaning up
Stop the process with Ctrl+C just like any framework script. The next run recreates the runtime
folder from scratch, ensuring you always start from a clean merge of the template when using
renoun dev.
After ejecting, the .renoun/ directory is automatically removed since the ejected app no longer
needs the runtime environment.
Security model
Running templates this way is designed with security in mind. Here’s how the system protects your project:
What renoun controls
The CLI uses an internal environment variable (RENOUN_RUNTIME_DIRECTORY) to tell renoun utilities
where the runtime directory is. This allows file discovery to work correctly in the merged
environment. Before trusting this variable, renoun validates it with multiple checks:
| Check | What it prevents |
|---|---|
| Real path resolution | Follows symlinks and resolves ../ before validation, preventing path traversal attacks |
| Containment verification | The resolved path must contain /.renoun/, ensuring it’s a renoun-managed directory |
| Workspace validation | The parent directory must contain package.json or pnpm-workspace.yaml, confirming it’s a real project |
If any check fails, renoun ignores the environment variable and falls back to normal path resolution. This means even if an attacker sets the variable, they can’t redirect renoun to arbitrary directories.
What you control
While renoun secures its internal operations, you should understand these aspects of the system:
- Template apps run code in your environment. Only install templates from sources you trust, just as you would with any npm dependency. Template apps have the same access as any other package in your project.
-
The
.renoun/directory is temporary. Add it to your.gitignore. It’s recreated fresh on each run, so don’t store anything important there. - Links point to your real files. When you override a file, the framework reads directly from your source. This is the intended behavior—it enables hot reload and live editing.
Shared environments
If you’re running renoun in a shared server or CI environment where other processes might set environment variables before the CLI starts, be aware that environment variable injection is a general security concern for any application. renoun’s validation mitigates this for its specific use case, but standard environment isolation practices still apply.
Summary
Using “Apps as Templates” via renoun dev is safe for normal development workflows. The security
model ensures renoun only operates within your project’s .renoun/ directory, and the same trust
model applies to template apps as to any npm package you install. If you eject, you keep all the
code locally and continue building as your own app with the same safety guarantees.