
How to deploy Nuxt to Vercel, Netlify, Cloudflare, and Node, with hybrid rendering via routeRules.
Reza Baar
June 3, 2026
Nuxt deploys almost anywhere thanks to Nitro, the server engine underneath. Nitro has over 15 presets that adapt your build output for different hosting targets. You can also mix rendering strategies in a single app: some routes static, some SSR, some client-only. In this post, we'll cover the most common deployment targets, how hybrid rendering works, and the environment variable patterns you need to get right.
When you run nuxt build, Nitro compiles your server code into a format that matches the target platform. The preset controls what the build output looks like:
In most cases, Nitro auto-detects the preset based on your deployment environment. If you're deploying to Vercel, it detects that and uses the Vercel preset automatically. You can also set it explicitly:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'cloudflare-pages',
},
});
Vercel auto-detects Nuxt projects. Push your code to a Git repo, connect it in the Vercel dashboard, and it builds and deploys with the right preset.
If you need to configure anything:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'vercel',
},
});
For Vercel Edge Functions (runs at the edge instead of a single region):
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'vercel-edge',
},
});
Environment variables are set in the Vercel dashboard under Settings > Environment Variables. They're available in your Nuxt app through useRuntimeConfig():
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
secretApiKey: '', // NUXT_SECRET_API_KEY
public: {
apiBase: '', // NUXT_PUBLIC_API_BASE
},
},
});
Nuxt automatically maps environment variables with the NUXT_ prefix to the corresponding runtimeConfig keys.
Similar to Vercel, Netlify auto-detects Nuxt. Connect your repo and it picks up the framework settings. Manually:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'netlify',
},
});
For Netlify Edge Functions:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'netlify-edge',
},
});
Set the build command to nuxt build and the publish directory to dist in your Netlify settings (or netlify.toml):
# netlify.toml
[build]
command = "nuxt build"
publish = "dist"
Cloudflare Pages runs your server code on the Workers runtime at the edge. This gives you low latency globally but comes with some constraints (no Node.js fs access, limited Node APIs).
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'cloudflare-pages',
},
});
Deploy with Wrangler:
npx nuxt build
npx wrangler pages deploy dist/
Or connect your Git repo in the Cloudflare dashboard. Make sure to disable Cloudflare's "Rocket Loader" and "Email Address Obfuscation" under Speed and Security settings, as these inject scripts that can cause hydration errors.
For a traditional server deployment (Docker, a VPS, or any environment with Node.js):
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'node-server',
},
});
Build and run:
nuxt build
node .output/server/index.mjs
The server starts on port 3000 by default. Set PORT and HOST environment variables to change this. Make sure NODE_ENV=production is set, because Vue Router strips development warnings only when this is present.
For Docker:
FROM node:20-slim
WORKDIR /app
COPY .output .output
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
The .output directory contains everything needed to run the app. You don't need node_modules or source files in the container.
If your entire site can be pre-rendered at build time:
npx nuxi generate
This crawls all routes and generates static HTML files. The output goes to .output/public and can be deployed to any static hosting (GitHub Pages, S3, Cloudflare Pages in static mode).
For sites with many dynamic routes, tell Nuxt which routes to pre-render:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
prerender: {
routes: ['/blog/post-1', '/blog/post-2'],
crawlLinks: true, // also pre-render pages linked from known routes
},
},
});
This is where Nuxt deployment gets interesting. You can assign different rendering strategies to different routes in a single app:
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Pre-rendered at build time (static)
'/': { prerender: true },
'/about': { prerender: true },
// Incremental Static Regeneration: cached, revalidated every hour
'/blog/**': { isr: 3600 },
// Server-side rendered on every request (default)
'/dashboard/**': { ssr: true },
// Client-only SPA mode (no server rendering)
'/admin/**': { ssr: false },
// Simple redirect
'/old-page': { redirect: '/new-page' },
// Cache headers for API routes
'/api/**': {
cors: true,
headers: { 'cache-control': 'max-age=60' },
},
},
});
This means your landing page and about page are static (fast, cached at CDN), your blog uses ISR (fresh content without rebuilding the entire site), your dashboard is fully SSR (always current), and your admin panel is client-only (no server rendering overhead).
The ** glob matches all paths under that prefix.
ISR is especially useful for content-heavy sites. The first request triggers a server render and caches the result. Subsequent requests get the cached version. After the isr interval (in seconds), the next request triggers a background revalidation.
routeRules: {
'/blog/**': { isr: 3600 }, // cache for 1 hour
'/products/**': { isr: 300 }, // cache for 5 minutes
}
This works on platforms that support it (Vercel, Netlify, Cloudflare). On a Node.js server, Nitro handles the caching internally.
The pattern is the same everywhere. Define the shape in runtimeConfig, set values through environment variables with the NUXT_ prefix:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
databaseUrl: '', // NUXT_DATABASE_URL (server only)
stripeSecret: '', // NUXT_STRIPE_SECRET (server only)
public: {
apiBase: '', // NUXT_PUBLIC_API_BASE (client + server)
siteUrl: '', // NUXT_PUBLIC_SITE_URL (client + server)
},
},
});
Access them in code:
// In a server route
const config = useRuntimeConfig();
const db = connectToDatabase(config.databaseUrl);
// In a component (only public config)
const config = useRuntimeConfig();
const apiUrl = `${config.public.apiBase}/posts`;
Where you set the actual values depends on the platform: Vercel/Netlify dashboards, Cloudflare's wrangler.toml, Docker .env files, or CI/CD pipeline variables. The runtimeConfig shape stays the same across all platforms.
After deploying, verify everything is working:
# Check which Nuxt/Nitro version and preset is active
npx nuxi info
# Preview the production build locally
npx nuxi preview
nuxi preview builds and serves the production output locally, using the same preset and rendering rules as the real deployment. This catches issues before they hit production.
.output directory is all you need.routeRules lets you mix static, ISR, SSR, and SPA rendering in one appNUXT_ prefix and map to runtimeConfig on all platformsnuxi preview to test production behavior locallyWe covered the most common deployment targets for Nuxt and how to configure hybrid rendering with routeRules. The combination of Nitro presets and per-route rendering strategies means you can optimize each part of your app independently without splitting it into separate projects.
Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

Events and Listeners: The Event System From the Inside Out
Laravel events are more than a way to decouple application logic. This article explores how the event system works under the hood, from dispatching and listeners to the internals that frequently appear on Laravel certification exams, helping you build a deeper understanding beyond basic usage.
Steve McDougall
Jun 11, 2026

Building 3D Scenes with TresJS
A hands-on walkthrough of TresJS, the Vue custom renderer for Three.js. From a blank canvas to a reactive 3D scene.
Reza Baar
Jun 10, 2026

`using` in JavaScript: Automatic Resource Management
Learn how the new using keyword and Symbol.dispose replace try/finally for cleaner resource management in JavaScript. With ES2026 support details.
Martin Ferret
Jun 9, 2026