This guide covers deploying your Nuxt 4 application to various hosting platforms.
Before deploying to production:
Vercel provides the best experience for Nuxt applications with zero configuration.
git push origin main
.env file..vercel.app domain.Vercel uses these defaults (no configuration needed):
{
"buildCommand": "pnpm build",
"outputDirectory": ".output/public",
"installCommand": "pnpm install"
}
netlify.toml:[build]
publish = ".output/public"
command = "pnpm build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[context.production.environment]
NUXT_PUBLIC_SITE_URL = "https://yourdomain.com"
pnpm build..output/public.# Build stage
FROM node:20-alpine AS build
WORKDIR /app
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy package files
COPY package.json pnpm-lock.yaml ./
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy source code
COPY . .
# Build application
RUN pnpm build
# Production stage
FROM node:20-alpine
WORKDIR /app
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy built application
COPY --from=build /app/.output ./.output
COPY --from=build /app/package.json ./
# Expose port
EXPOSE 3000
# Set environment
ENV NODE_ENV=production
ENV PORT=3000
# Start application
CMD ["node", ".output/server/index.mjs"]
version: '3.8'
services:
app:
build: .
ports:
- '3000:3000'
environment:
- DATABASE_URL=${DATABASE_URL}
- NUXT_PUBLIC_SITE_URL=${NUXT_PUBLIC_SITE_URL}
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
depends_on:
- db
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:
# Build image
docker build -t myapp .
# Run container
docker run -p 3000:3000 \
-e DATABASE_URL="..." \
-e STRIPE_SECRET_KEY="..." \
myapp
# Or use docker-compose
docker-compose up -d
pnpm build.node .output/server/index.mjs.# Connect to instance
ssh ubuntu@your-instance-ip
# Update system
sudo apt update && sudo apt upgrade -y
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Install pnpm
npm install -g pnpm
# Install PM2
npm install -g pm2
# Clone repository
git clone https://github.com/youruser/yourrepo.git
cd yourrepo
# Install dependencies
pnpm install
# Build application
pnpm build
# Start with PM2
pm2 start .output/server/index.mjs --name myapp
# Save PM2 configuration
pm2 save
pm2 startup
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
# Enable site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Install SSL with Certbot
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
NODE_ENV=production
NUXT_PUBLIC_SITE_URL="https://yourdomain.com"
DATABASE_URL="postgresql://prod-db:5432/myapp"
STRIPE_SECRET_KEY="sk_live_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
# Analytics (optional)
NUXT_PUBLIC_UMAMI_ID="prod-website-id"
NUXT_PUBLIC_UMAMI_HOST="https://analytics.yourdomain.com"
NUXT_PUBLIC_VERCEL_ANALYTICS="true"
NODE_ENV=staging
NUXT_PUBLIC_SITE_URL="https://staging.yourdomain.com"
DATABASE_URL="postgresql://staging-db:5432/myapp_staging"
STRIPE_SECRET_KEY="sk_test_..."
# Analytics (optional)
NUXT_PUBLIC_UMAMI_ID="staging-website-id"
NUXT_PUBLIC_UMAMI_HOST="https://analytics.yourdomain.com"
Run Prisma migrations in production:
# In your CI/CD pipeline or deployment script
pnpm prisma migrate deploy
prisma migrate dev in production. Use migrate deploy instead.Add a health check endpoint:
export default defineEventHandler(async () => {
// Check database connection
try {
await prisma.$queryRaw`SELECT 1`
return {
status: 'healthy',
timestamp: new Date().toISOString(),
}
} catch (error) {
throw createError({
statusCode: 503,
message: 'Service unavailable',
})
}
})
Use in your monitoring:
curl https://yourdomain.com/api/health
pnpm add @sentry/nuxt
export default defineNuxtConfig({
modules: ['@sentry/nuxt/module'],
sentry: {
dsn: process.env.SENTRY_DSN,
},
})
export default defineNuxtConfig({
nitro: {
compressPublicAssets: true,
routeRules: {
'/': { prerender: true },
'/api/**': { cors: true, headers: { 'cache-control': 's-maxage=60' } },
},
},
})
Use @nuxt/image for automatic optimization:
<NuxtImg src="/image.jpg" loading="lazy" format="webp" quality="80" />
Nuxt automatically code-splits by route. For large components:
<script setup>
const HeavyComponent = defineAsyncComponent(() => import('./HeavyComponent.vue'))
</script>
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm test
- name: Build
run: pnpm build
env:
NUXT_PUBLIC_SITE_URL: ${{ secrets.SITE_URL }}
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
Check Node.js version:
node --version # Should be 20.x or higher
Clear cache:
rm -rf .nuxt .output node_modules
pnpm install
pnpm build
Check logs:
docker logs container-name.pm2 logs myapp.Check environment variables:
# Verify variables are set
env | grep NUXT_
After successful deployment: