Render outside Nitro
Compile the same Vue templates for a Node backend without booting Nuxt.
Build a server-only email registry before you deploy your backend. The registry uses the same rendering core as Nitro and the development preview. Your application still owns delivery.
Compile the registry
The output directory belongs to the build. Do not put source files there. The helper rejects an existing directory that it did not create. Do not import the generated registry into browser code.
import { buildEmailRegistry } from '@lupinum/nuxt-email/build'
await buildEmailRegistry({
rootDir: '.',
outDir: 'generated/emails',
})Run this script from the application root before the backend build. Run it again when templates or their imports change. Do not run it inside a request, action, or background delivery job.
rootDir is the project root. Templates default to app/emails. For a custom
Nuxt srcDir, pass templatesDir: 'src/emails', relative to rootDir. The helper
does not load Nuxt configuration. Relative outDir paths also use rootDir.
The output must remain inside the project root, outside the template directory,
so build-time declarations and the generated module can resolve project dependencies.
The helper generates:
generated/emails/index.mjs: compiled Vue components and the runtime registry.generated/emails/index.d.mts: template names and inferred prop types.generated/emails/types/: Vue-generated declarations and their local imports.
These files are derived output. Keep them out of version control and regenerate them in the deployment build. Ordinary TypeScript reads the declarations without a Vue compiler. A compilation failure leaves the previous successful output unchanged. Do not run concurrent builds against the same output path.
Render with runtime data
import { renderEmail } from '../generated/emails/index.mjs'
const email = await renderEmail('welcome', {
firstName: 'Ada',
activationUrl: 'https://example.com/activate',
})The template controls the props. This example uses the welcome template from the
quickstart. The result contains html, text, and an optional subject.
Validate external input before calling the renderer.
If your build already compiles Vue components, use the production renderer directly:
import { renderEmailComponent } from '@lupinum/nuxt-email/render'
import Welcome from './compiled/welcome.js'
const email = await renderEmailComponent(Welcome, {
firstName: 'Ada',
activationUrl: 'https://example.com/activate',
})Keep templates portable
Keep local imports inside the email directory. Import Vue helpers and
defineEmail explicitly. Nuxt aliases, plugins, auto-imported composables and
application state are not available in this build. The existing email components
remain registered by the renderer. Put reusable components in components/ and
import them explicitly from templates.
The standalone compiler supports HTML templates and JavaScript or TypeScript
scripts, including script setup, async setup, and template-only components.
It rejects SFC style blocks, custom blocks, external blocks (src), JSX and
template preprocessors. Use ETailwind or inline styles. Nitro keeps its existing
Vue compiler integration; this portable subset does not replace that integration
or claim support for every feature in a Nuxt application.
This first standalone build does not configure ECodeBlock. Use Nitro for
templates that require configured syntax highlighting. There is no silent
fallback renderer. Preview fixtures are not discovered or imported by the
production registry.
The build uses Vue's SFC compiler, esbuild, TypeScript and vue-tsc. They are build dependencies of the package, not imports of the production render entry. The runtime registry leaves package dependencies external; the application's backend bundler must include or resolve them.
Verify the deployment
Node package rendering and generated declaration checks are separate from a hosted deployment test. Convex Node actions are a target for integration testing; this package change does not certify a deployed Convex application. The default Convex runtime and edge workers are not supported claims.
Before production use, test the packed package in your actual backend, including concurrent renders, dynamic props, fonts and Tailwind output. Measure cold and warm execution. Never make auth delivery depend on an unverified rendering path.