Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Vue 3 SSR with Nuxt 3 Complete Guide

By Kokil Thapa | Last reviewed: August 2026

Building a performant, SEO-friendly frontend requires understanding the server-client boundary, and this Vue 3 SSR with Nuxt 3 complete guide provides the production-focused blueprint you need. While traditional SPAs struggle with initial load performance and crawlability, Nuxt 3’s hybrid rendering engine solves these issues by default when configured correctly. For developers transitioning from backend frameworks like Laravel or managing content-heavy sites, mastering this architecture is essential for delivering fast user experiences that rank well. If you are evaluating frontend options alongside backend work, understanding how single page apps compare to multi-page sites for SEO helps clarify why SSR remains the standard for public-facing applications.

How Do You Configure Vue 3 SSR with Nuxt 3 for Production?

Nuxt 3 defaults to universal (SSR) rendering, but production readiness requires explicit configuration beyond scaffolding. The primary control surface is nuxt.config.ts, where you define rendering modes, route rules, and build optimizations. In my experience shipping frontend applications alongside Laravel APIs, treating this configuration as infrastructure code rather than application logic prevents subtle hydration mismatches and performance regressions.

Setting Rendering Modes and Route Rules

Not every route needs server rendering. Static marketing pages benefit from pre-rendering (SSG), while authenticated dashboards should remain client-side only. Nuxt 3’s routeRules allow granular control without separate projects:

<script lang="ts">
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true, // Global SSR toggle
  routeRules: {
    // Pre-render static content at build time
    '/': { prerender: true },
    '/about': { prerender: true },
    
    // Cache dynamic pages on CDN/edge for 1 hour
    '/products/': { swr: 3600 },
    
    // Force SPA mode for authenticated routes
    '/dashboard/': { ssr: false },
    
    // Redirect legacy URLs
    '/old-blog/': { redirect: '/blog/' }
  },
  
  nitro: {
    preset: 'node-server', // Default for Node hosting
    compressPublicAssets: true,
    minify: true
  }
})
</script>

The swr (stale-while-revalidate) strategy is particularly valuable for high-traffic e-commerce or directory sites. It serves cached HTML instantly while revalidating in the background, balancing freshness with performance. For Nepal-based businesses with variable traffic patterns during festivals like Dashain, this prevents origin server overload while keeping product listings current.

Environment-Specific Configuration

Production builds must handle environment variables securely. Never expose API secrets to the client bundle. Nuxt 3 distinguishes between runtimeConfig.public (client-safe) and private server-only config:

<script lang="ts">
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // Server-only: never sent to browser
    databaseUrl: process.env.DATABASE_URL,
    paymentSecret: process.env.PAYMENT_SECRET,
    
    // Public: accessible via useRuntimeConfig()
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || 'https://api.example.com',
      gaId: process.env.NUXT_PUBLIC_GA_ID
    }
  }
})
</script>

This separation mirrors backend framework patterns where sensitive credentials stay server-side. When integrating with Laravel APIs or payment gateways like eSewa or Khalti, always validate that secrets remain in the private config block. A common mistake is accidentally exposing keys through public config, creating security vulnerabilities that only surface in production audits.

Browser RequestInitial Page LoadNitro ServerRender HTML + DataExecute useAsyncDataSerialize PayloadHTML ResponseFully Rendered ContentClient HydrationAttach Event ListenersReuse Server DataClient NavigationFetch JSON Payload OnlyNo Full Page ReloadSubsequent navigations bypass SSR entirelyreducing server load and improving UX
Nuxt 3 SSR request lifecycle: initial requests render on server, hydrate on client, then navigate via lightweight JSON payloads

What Is the Correct Data Fetching Pattern in Nuxt 3 SSR?

Data fetching is where most SSR implementations fail. Using Vue’s onMounted or plain fetch breaks server rendering because these execute only in the browser. Nuxt 3 provides useAsyncData and useFetch composables that run on both server and client, automatically serializing data into the HTML payload to prevent duplicate requests during hydration.

Using useAsyncData vs useFetch

Choose based on your data source:

  • useFetch: Shortcut for HTTP requests to external APIs or your own Nitro endpoints. Handles loading states, errors, and caching automatically.
  • useAsyncData: Generic wrapper for any async operation (database queries via server routes, complex transformations, third-party SDKs).
<script setup lang="ts">
// ✅ CORRECT: Works on server AND client
const { data: products, pending, error } = await useFetch('/api/products', {
  query: { category: 'electronics' },
  transform: (response) => response.data.map(p => ({
    id: p.id,
    name: p.title,
    price: p.price_npr
  }))
})

// ✅ CORRECT: Custom async logic
const { data: userStats } = await useAsyncData('user-stats', () => 
  $fetch('/api/analytics/user-summary')
)

// ❌ WRONG: Only runs in browser, causes hydration mismatch
onMounted(async () => {
  const res = await fetch('/api/products')
  products.value = await res.json()
})
</script>

The key insight is that useAsyncData uses a unique key (first argument or auto-generated from URL) to deduplicate requests. During SSR, data fetches execute once, serialize into __NUXT_DATA__ script tags, and hydrate without refetching. On client navigation, Nuxt checks if cached data exists before making network requests.

Handling Authentication and Cookies

Server-side requests don’t automatically include browser cookies. When fetching user-specific data during SSR, explicitly forward cookies using useRequestHeaders:

<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])

const { data: profile } = await useFetch('/api/me', {
  headers, // Forward auth cookie to API
  server: true // Ensure this runs on server
})
</script>

This pattern is critical for legal-tech portals or membership sites where personalized content must render server-side for SEO while respecting authentication state. Without forwarding headers, the server sees an unauthenticated request and renders generic content, causing hydration mismatches when the client loads authenticated data.

How Does Nuxt 3 Compare to Other Vue SSR Solutions?

Choosing an SSR framework involves trade-offs between developer experience, ecosystem maturity, and operational complexity. While alternatives exist, Nuxt 3’s integrated approach reduces configuration overhead for most production use cases.

FeatureNuxt 3Vite SSR (Manual)Quasar SSR
Setup ComplexityZero-config SSR out of boxManual Vite plugin + entry pointsCLI scaffold, moderate config
Data FetchingBuilt-in composables with serializationCustom implementation requiredPreFetch mixin, less ergonomic
File-Based RoutingYes, with middleware supportNo, manual router setupYes, Vue Router integration
Hybrid RenderingPer-route SSG/SWR/SPA rulesNot supported nativelyLimited, global mode only
Server EngineNitro (cross-platform, edge-ready)Express/Fastify custom setupNode.js built-in adapter
Ecosystem Modules200+ official/community modulesDIY integrationsSmaller module ecosystem
Best ForContent sites, e-commerce, full-stack appsCustom architectures, learning SSREnterprise apps with Quasar UI

For teams already invested in the Vue ecosystem, Nuxt 3 offers the fastest path to production SSR. Manual Vite SSR makes sense only when you need complete control over the build pipeline or are embedding SSR into an existing non-Nuxt application. Quasar serves niche enterprise use cases where its component library justifies the trade-offs.

Nuxt 3 Integrated StackVue AppNitro ServerAuto-Wired: Routes, Data, SEO, Build✓ Zero ConfigSingle dependency, unified toolingManual Vite SSRVue AppVite PluginExpressManual: Entry Points, Serialization, Routing✗ High Config BurdenMultiple deps, custom glue codeRecommended for 90% of ProjectsFaster delivery, fewer bugs, better DXOnly for Specialized NeedsCustom infra, learning, embedded SSR
Nuxt 3 integrates routing, data fetching, and server engine into one coherent system versus assembling disparate tools manually

How Do You Optimize SEO and Metadata in Nuxt 3?

Server rendering alone doesn’t guarantee good SEO. Search engines need proper metadata, structured data, and canonical URLs. Nuxt 3’s useHead and useSeoMeta composables provide type-safe, reactive metadata management that works universally across SSR and SSG.

Dynamic Page Metadata

Every page should define unique title, description, and Open Graph tags. Use computed properties for dynamic content:

<script setup lang="ts">
const { data: article } = await useFetch('/api/articles/vue-ssr-guide')

useSeoMeta({
  title: () => article.value?.title || 'Default Title',
  description: () => article.value?.excerpt || 'Default description',
  ogTitle: () => article.value?.title,
  ogDescription: () => article.value?.excerpt,
  ogImage: () => article.value?.featured_image,
  twitterCard: 'summary_large_image'
})

// Advanced: Add structured data
useHead({
  script: [{
    type: 'application/ld+json',
    innerHTML: JSON.stringify({
      '@context': 'https://schema.org',
      '@type': 'Article',
      headline: article.value?.title,
      author: { '@type': 'Person', name: 'Kokil Thapa' },
      datePublished: article.value?.published_at
    })
  }]
})
</script>

For legal-tech sites or service directories, structured data significantly improves rich snippet eligibility. I’ve seen law firm portals gain substantial organic visibility by properly marking up attorney profiles, practice areas, and FAQ sections. Always validate schema markup with Google’s Rich Results Test before deploying.

Canonical URLs and Duplicate Content Prevention

Nuxt 3 doesn’t auto-generate canonicals. Implement them globally via middleware or per-page:

<script setup lang="ts">
const route = useRoute()
const config = useRuntimeConfig()

useHead({
  link: [{
    rel: 'canonical',
    href: `${config.public.siteUrl}${route.path}`
  }]
})
</script>

This prevents duplicate content issues from query parameters, trailing slashes, or UTM tracking codes. For sites serving both NPR and USD pricing (common in Nepal e-commerce), ensure currency variants use canonical tags pointing to the primary version unless each currency targets distinct geographic markets.

How Do You Deploy Nuxt 3 SSR Applications to Production?

Nuxt 3 builds to a standalone Node.js server via Nitro. Deployment differs fundamentally from static site hosting — you need a persistent Node.js process, not just file serving. Understanding this distinction prevents costly hosting mistakes.

Build and Output Structure

Run npx nuxi build to generate the production artifact. The output lives in .output/:

  • .output/server/index.mjs — Standalone Node.js entry point
  • .output/public/ — Static assets (CSS, JS, images)
  • .output/nitro.json — Runtime configuration metadata

This directory is self-contained. Copy it to your server, install production dependencies (npm ci --production in .output/server if needed), and run node .output/server/index.mjs. No source code, node_modules, or build tools required on the production server.

Process Management and Reverse Proxy

Never run Node.js directly in production. Use PM2 or systemd for process management, and Nginx/Apache as a reverse proxy for SSL termination and static asset caching:

# /etc/nginx/sites-available/nuxt-app
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;
    
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # Serve static assets directly, bypass Node
    location /_nuxt/ {
        alias /var/www/nuxt-app/.output/public/_nuxt/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Proxy all other requests to Node
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

This setup mirrors deployments for Laravel or Symfony applications where PHP-FPM sits behind Nginx. For teams managing multiple sites on shared infrastructure (like the sister sites I maintain via Deployer 7), this pattern allows consistent operations across different tech stacks. Static assets served directly by Nginx reduce Node.js load by 60–80% on typical content sites.

InternetHTTPS RequestsNginx Reverse ProxySSL TerminationGzip CompressionSecurity HeadersStatic: /_nuxt/* → DiskDynamic: /* → ProxyNode.js + NitroSSR RenderingAPI RoutesMiddleware ExecutionPM2 / systemd managedExternal ServicesLaravel API • Database • RedisStatic assets bypass Node entirely, reducing server load and latency
Production Nuxt 3 deployment: Nginx handles SSL and static files, proxies dynamic requests to Node.js Nitro server

Platform-Specific Presets

Nitro supports 20+ deployment presets. Override the default node-server preset for platform-optimized builds:

<script lang="ts">
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    // Choose based on hosting target
    preset: 'vercel'     // Vercel Edge/Serverless
    // preset: 'cloudflare-pages'  // Cloudflare Workers
    // preset: 'aws-lambda'        // AWS Lambda
    // preset: 'docker'            // Containerized deployment
  }
})
</script>

Each preset optimizes output format, cold start behavior, and platform-specific features. For Nepal-based clients with budget constraints, self-hosted Node.js on a VPS (Rs 1,500–3,000/month, ~USD 11–22) often costs less than serverless platforms at scale, especially for consistently trafficked sites. Evaluate actual usage patterns before choosing premium managed hosting.

Moving Forward with Vue 3 SSR

This Vue 3 SSR with Nuxt 3 complete guide covers the production essentials: correct data fetching patterns, hybrid rendering configuration, SEO optimization, and reliable deployment. The framework handles complexity well when you respect its conventions — avoid fighting the SSR model with client-only patterns, leverage route rules for intelligent caching, and treat metadata as first-class application code. For teams evaluating whether to adopt Nuxt 3 or seeking help with an existing implementation, reach out to discuss your specific requirements. Whether you’re building a legal-tech portal, e-commerce platform, or content site, getting the SSR foundation right prevents costly rewrites down the line. Explore more frontend and backend integration strategies in our Livewire tutorial for Laravel developers or learn about website speed’s direct impact on SEO rankings for Nepal-focused projects.

Frequently Asked Questions

Nuxt 3 requires Node.js 22 LTS or Node.js 20 LTS. Older versions like Node 18 are end-of-life and unsupported. Always verify with nuxi info after installation to confirm your runtime meets current framework requirements before starting development or deploying to production servers.

Standard SPAs render entirely in the browser, causing delayed content visibility and poor SEO. Vue 3 SSR generates full HTML on the server for each request, delivering immediate content to crawlers and users. This improves Core Web Vitals and indexation but increases server load compared to static client-side rendering.

Yes, if SEO and initial load performance matter more than minimal hosting costs. For simple brochure sites, WordPress or static HTML may suffice. But for legal-tech portals or service directories requiring dynamic content with strong search visibility, Nuxt 3 SSR justifies the added infrastructure complexity and Rs 3,000–8,000 monthly hosting.

Set ssr: true in your nuxt.config.ts file, which is the default. Ensure you have a Node.js runtime available in production. For hybrid rendering, use routeRules to define specific paths as static, ISR, or SSR. Test locally with npm run build followed by node .output/server/index.mjs to verify server behavior before deployment.

Hydration mismatches occur when server-rendered HTML differs from client-side Vue output. Common causes include using Date.now(), Math.random(), or browser-only APIs during render. Wrap non-deterministic code in onMounted or use . Enable debug: true in nuxt.config.ts to identify exact mismatch locations during development.

Absolutely. In my experience building legal-tech platforms, Nuxt 3 works excellently as a frontend consuming Laravel REST APIs. Use useFetch or $fetch for server-side data retrieval during SSR. Configure API base URLs via runtime config to handle different endpoints for server and client contexts securely without exposing credentials.

Expect Rs 4,000–12,000 monthly (~USD 30–90) for managed Node hosting or VPS. Shared hosting rarely supports SSR adequately. Self-hosting on Ubuntu with PM2 reduces costs to Rs 2,500–5,000 but requires DevOps skills. Factor in SSL, backups, and monitoring. Static generation eliminates server costs but sacrifices dynamic SSR benefits.

Choose static generation when content changes infrequently and you want zero server costs. Use SSR when content is user-specific, frequently updated, or requires real-time data. Hybrid rendering lets you mix both: static for marketing pages, SSR for dashboards or search results. Evaluate based on update frequency, personalization needs, and budget constraints.

Minimize JavaScript bundles with code splitting and lazy loading. Use server components for heavy UI elements. Optimize images with nuxt-image module. Implement proper caching headers and CDN distribution. Reduce TTFB by optimizing database queries in API layer. Monitor LCP, FID, and CLS via Lighthouse. SSR helps FCP but large payloads hurt interactivity metrics.

Never expose API keys in client bundles; use server-only runtime config. Validate all user inputs server-side, not just in Vue components. Implement rate limiting on SSR routes to prevent abuse. Keep dependencies updated via npm audit. Use HTTPS everywhere. Sanitize any user-generated content rendered server-side to prevent XSS. Restrict server filesystem access and environment variables.

Nuxt 3 uses Vue 3 composition API while Next.js uses React. Nuxt offers superior developer experience for Vue developers with auto-imports and file-based routing. Next.js has larger ecosystem and Vercel integration. Both support SSR, SSG, and ISR effectively. Choose based on team expertise. For Vue-focused teams in Nepal, Nuxt 3 reduces context switching and leverages existing Vue knowledge.

Yes, but payment processing must happen server-side for security. Create Nuxt server routes that handle payment gateway callbacks and verification. Never expose merchant credentials in client code. Use useFetch to initiate transactions from SSR pages. On projects like Nepal Gift Card, I implemented this pattern successfully with Laravel backend handling actual payment logic while Nuxt managed the user-facing checkout flow.

Use cookies for session persistence across SSR and client hydration. Store JWT tokens in httpOnly cookies, never localStorage. Create middleware to validate sessions server-side before rendering protected routes. Use useState for reactive auth state that transfers from server to client during hydration. Implement proper token refresh logic. Avoid storing sensitive user data in composables that serialize to HTML.

Enable debug: true in nuxt.config.ts for hydration warnings. Use Vue DevTools browser extension for component inspection. Add nuxt-debugbar module for server-side profiling. Check .nuxt/dist/server logs for runtime errors. Use console.log strategically in server routes and composables. For performance issues, analyze bundle size with nuxt analyze command. Production debugging requires structured logging since browser devtools cannot inspect server renders.

Build with npm run build, then start using pm2 start .output/server/index.mjs --name nuxt-app. Configure ecosystem.config.cjs for environment variables and cluster mode. Set up Nginx reverse proxy with proper buffering disabled. Enable PM2 startup script for auto-restart. Monitor with pm2 monit. Ensure Node.js 22 LTS is installed. In my deployments, this setup handles moderate traffic reliably at Rs 3,000–6,000 monthly VPS cost.

Share this article

Quick Contact Options
Choose how you want to connect me: