Deploying Nuxt to Cloudflare Pages: The Complete Walkthrough
Step-by-step guide to deploying a Nuxt 3 or Nuxt 4 app to Cloudflare Pages with edge SSR, environment variables, KV storage, and custom domains.

James Ross Jr.
Strategic Systems Architect & Enterprise Software Developer
Cloudflare Pages has become my default deployment target for Nuxt applications that do not need a full server. Free tier is genuinely generous, the edge network is excellent, and the developer experience has improved substantially over the past year. When you combine it with Nuxt's Nitro server, you get SSR running at the edge — globally distributed, fast, and cheap to operate.
This guide covers the complete setup: initial deployment, environment variables, edge SSR configuration, KV storage for caching, and custom domains. I am assuming you have a working Nuxt application and a Cloudflare account.
Understanding What "Edge SSR" Means
Before getting into the setup, it is worth understanding what you are actually deploying. Cloudflare Pages with edge SSR uses Cloudflare Workers under the hood. Your Nuxt server code runs in Cloudflare's edge runtime — a V8-based JavaScript environment that runs in over 300 data centers worldwide.
When a user in Tokyo requests your page, the Worker runs in a Tokyo data center, renders the Nuxt page, and returns HTML. There is no round trip to a central server. The latency difference between a global CDN and a single-region server can be 300-500ms for distant users. For Core Web Vitals, that difference is significant.
The trade-off is that the Cloudflare Workers runtime is not Node.js. Not all Node.js APIs are available. If your server code depends on fs, child_process, or Node.js-specific modules, it will not work. Most Nuxt applications do not need these — but it is worth checking before committing to this deployment target.
Project Configuration
Configure Nuxt to target the Cloudflare Pages preset:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'cloudflare-pages',
},
})
That is the only required configuration change. Nuxt and Nitro handle the rest of the build output formatting for Cloudflare Pages compatibility.
Install the Cloudflare Pages adapter if you plan to use Cloudflare-specific features like KV or D1:
npm install wrangler --save-dev
Setting Up Cloudflare Pages
Log in to the Cloudflare dashboard and create a new Pages project:
- Go to Workers & Pages
- Click "Create application" then "Pages"
- Connect to Git (GitHub or GitLab)
- Select your repository
Configure the build settings:
Build command: npm run build
Build output dir: .output/public
Root directory: / (or your project subdirectory)
Set the Node.js version to match your local development environment. Cloudflare Pages supports Node.js 18 and 20. Set this in the environment variables section:
NODE_VERSION = 20
Environment Variables
Cloudflare Pages has two types of environment variables: plain variables and secrets. Both are set in the dashboard under Settings > Environment variables.
Add them for the Production environment (and Preview if needed):
DATABASE_URL = postgresql://...
API_KEY = your-api-key
NUXT_PUBLIC_API_BASE = https://api.yourdomain.com
Variables prefixed with NUXT_PUBLIC_ are automatically available in the browser. Variables without this prefix are server-only. Never put secrets in NUXT_PUBLIC_ variables — they will be visible in the JavaScript bundle.
Access them in your Nuxt application:
// composables/useConfig.ts
export function useConfig() {
const config = useRuntimeConfig()
return {
apiBase: config.public.apiBase, // Available client + server
apiKey: config.apiKey, // Server only
}
}
// nuxt.config.ts
runtimeConfig: {
apiKey: '', // Override with NUXT_API_KEY env var
public: {
apiBase: 'https://api.example.com' // Override with NUXT_PUBLIC_API_BASE
}
}
Using Cloudflare KV for Caching
KV (Key-Value) storage is Cloudflare's globally replicated edge storage. You can use it to cache API responses, session data, or any string/blob data that benefits from edge proximity.
Create a KV namespace in the Cloudflare dashboard, then bind it to your Pages project:
- Settings > Functions > KV namespace bindings
- Add a binding: Variable name =
CACHE, KV namespace = your namespace
Access the KV store in Nuxt server routes:
// server/api/products.ts
export default defineEventHandler(async (event) => {
const cf = event.context.cloudflare
const cacheKey = 'products:all'
// Try cache first
const cached = await cf.env.CACHE.get(cacheKey, 'json')
if (cached) return cached
// Fetch from your API
const products = await $fetch('https://api.yourdomain.com/products')
// Cache for 5 minutes
await cf.env.CACHE.put(cacheKey, JSON.stringify(products), {
expirationTtl: 300,
})
return products
})
This pattern gives you API response caching at the edge with zero cold start. Requests that hit the KV cache return in under 10ms globally.
Wrangler for Local Development
To test Cloudflare-specific features locally, use Wrangler:
# Build your Nuxt app
npm run build
# Serve locally with Cloudflare Workers runtime
npx wrangler pages dev .output/public
This runs your application in the actual Workers runtime, not Node.js, so you catch Workers-incompatible code before deploying. I run this as a final check before every production deployment when I have made changes to server routes.
Add a local KV namespace for development:
# wrangler.toml
[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"
preview_id = "your-preview-kv-namespace-id"
Custom Domains
Adding a custom domain to a Cloudflare Pages project is straightforward if your domain is managed by Cloudflare (which it should be — Cloudflare's DNS is excellent):
- Settings > Custom domains
- Enter your domain
- Cloudflare automatically creates the DNS records
If your domain is with another registrar, add a CNAME record pointing to your-project.pages.dev. SSL is automatic and included.
Preview Deployments
Every pull request automatically gets a preview deployment at a unique URL like your-branch.your-project.pages.dev. This is one of the best features of Cloudflare Pages for team workflows — stakeholders can review changes before they hit production.
You can add comments to pull requests linking to the preview URL, or use Cloudflare's GitHub integration to post deployment status directly to PRs.
Performance Tuning for Edge Deployment
A few patterns that improve edge performance:
Minimize cold start time. Cloudflare Workers have no cold start in the traditional sense, but they do have a global JavaScript bundle size limit (1MB compressed for free tier, 5MB for paid). Keep your server-side code lean. Do not import large Node.js libraries.
Use routeRules for route-level caching:
// nuxt.config.ts
routeRules: {
'/': { swr: 3600 }, // Cache homepage for 1 hour
'/blog/**': { swr: 86400 }, // Cache blog posts for 24 hours
'/api/**': { cors: true }, // API routes, no caching
'/dashboard/**': { ssr: true }, // Always SSR, no cache
}
Stream responses for large pages. Nuxt's streaming support allows the browser to start rendering before the server finishes generating the complete page.
Troubleshooting Common Issues
"Cannot find module" errors at runtime: A Node.js module you are importing is not available in the Workers runtime. Check Cloudflare's Node.js compatibility docs and find a Workers-compatible alternative.
Environment variables undefined: Verify the variable name matches exactly (case-sensitive) and that you have deployed after adding the variable. Preview and Production environments have separate variable sets.
KV binding undefined: Make sure the binding name in the dashboard matches the property name you are accessing on cf.env. The binding name is case-sensitive.
Build fails on Cloudflare: Run npm run build locally first to catch issues before they fail in CI. Cloudflare's build logs are detailed but debugging through the dashboard is slower than local iteration.
Cloudflare Pages is an excellent deployment target for Nuxt applications. The free tier is sufficient for most personal and small business sites, the edge performance is genuine (not marketing), and the integration with the rest of the Cloudflare ecosystem is increasingly powerful.
Deploying a Nuxt application and running into issues with the infrastructure, or want help designing a deployment strategy that fits your team's workflow? I am happy to help — book a call at calendly.com/jamesrossjr.