Skip to content
Nuxt.js Guide — Vue.js Framework for Server-Side Rendering

Nuxt.js Guide — Vue.js Framework for Server-Side Rendering

DodaTech Updated Jun 7, 2026 9 min read

Nuxt.js is a powerful Vue.js framework that simplifies building server-side rendered (SSR), statically generated (SSG), and client-side rendered Vue applications with conventions like file-based routing, auto-imports, and a modular plugin ecosystem.

What You’ll Learn

You’ll understand Nuxt’s directory conventions, configure SSR vs SSG, navigate file-based routing, fetch data with useFetch and useAsyncData, apply middleware, extend with modules, and deploy Nuxt apps to production.

Why Nuxt.js Matters

Nuxt.js eliminates the hardest parts of configuring Vue for production: routing, SSR hydration, build tooling, and data fetching. It’s the recommended framework for any serious Vue project that needs SEO, performance, or scalability. Doda Browser’s documentation portal uses Nuxt 3 with static generation for fast page loads and excellent search engine rankings.

Nuxt.js Learning Path

    flowchart LR
  A[Vue.js Basics] --> B[Nuxt.js]
  B --> C[File-Based Routing]
  C --> D[Data Fetching]
  D --> E[Middleware & Plugins]
  E --> F[Modules & Build Config]
  F --> G[Deploy]
  B:::current

  classDef current fill:#00C58E,color:#fff,stroke:#333,stroke-width:2px
  
Prerequisites: Solid Vue fundamentals (components, reactivity, composables). Familiarity with JavaScript modules and Node.js is expected.

How Nuxt.js Differs from Standard Vue

Standard Vue apps need manual setup for routing, SSR, and build tooling. Nuxt provides all of this out of the box:

  • File-based routing — Every .vue file in pages/ becomes a route automatically
  • Auto-imports — Composables, components, and utilities are imported automatically
  • Hybrid rendering — SSR, SSG, or CSR per route, configurable in nuxt.config.ts
  • Server routes — API endpoints live in server/ alongside your frontend

Project Structure

A Nuxt 3 project follows a convention-based structure:

my-nuxt-app/
├── app.vue              # Root component
├── pages/               # File-based routes
│   ├── index.vue
│   ├── about.vue
│   └── blog/
│       ├── index.vue
│       └── [slug].vue
├── components/          # Auto-imported components
├── composables/         # Auto-imported composables
├── layouts/             # Page layouts
├── middleware/          # Route middleware
├── server/              # Server routes & API
├── public/              # Static assets
└── nuxt.config.ts       # Configuration

Each folder has a specific purpose. The pages/ directory is the most important — its file structure maps directly to URL paths.

File-Based Routing

Nuxt’s file-based routing is intuitive and eliminates manual route configuration.

Basic Routes

FileURL Path
pages/index.vue/
pages/about.vue/about
pages/contact.vue/contact

Dynamic Routes

Use square brackets for URL parameters:

<!-- pages/blog/[slug].vue -->
<template>
  <article>
    <h1>{{ post.title }}</h1>
    <div>{{ post.content }}</div>
  </article>
</template>

<script setup>
const route = useRoute();
const slug = route.params.slug;

// Fetch post by slug
const { data: post } = await useFetch(`/api/posts/${slug}`);
</script>

Output: Visiting /blog/hello-world renders the component with slug set to "hello-world" and fetches the matching blog post from the API.

Nested Routes

Directories create nested routes automatically:

<!-- pages/dashboard/settings.vue -->
<template>
  <div>
    <h1>Dashboard Settings</h1>
    <p>Configure your preferences here.</p>
  </div>
</template>

Output: Accessible at /dashboard/settings with no route configuration needed.

Data Fetching

Nuxt 3 provides two composables for data fetching on the server and client.

useFetch

The simplest way to fetch data — works in both SSR and client-side contexts:

<script setup>
const { data: users, pending, error, refresh } = useFetch('/api/users', {
  method: 'GET',
  headers: { 'Content-Type': 'application/json' }
});

// useFetch automatically handles:
// - Server-side fetching during SSR
// - Client-side hydration without re-fetching
// - Loading and error states
// - Deduplication of identical requests
</script>

<template>
  <div>
    <p v-if="pending">Loading users...</p>
    <p v-else-if="error">Failed to load: {{ error.message }}</p>
    <ul v-else>
      <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>
</template>

Output: During SSR, users are fetched on the server and HTML is rendered with the data. On the client, the component hydrates without re-fetching. Subsequent navigations fetch client-side.

useAsyncData

More control than useFetch, suitable for custom fetching logic:

<script setup>
const { data: stats } = useAsyncData('dashboard-stats', async () => {
  // Custom async logic
  const response = await fetch('https://api.example.com/stats');
  const json = await response.json();
  
  // Transform data before returning
  return {
    totalUsers: json.users.length,
    activeToday: json.users.filter(u => u.lastSeen > Date.now() - 86400000).length,
    averageSession: json.users.reduce((sum, u) => sum + u.sessionDuration, 0) / json.users.length
  };
});

// useAsyncData accepts a unique key ('dashboard-stats') to prevent
// duplicate fetches across components
</script>

<template>
  <div class="stats-grid">
    <div class="stat-card">
      <h3>Total Users</h3>
      <p class="stat-value">{{ stats.totalUsers }}</p>
    </div>
    <div class="stat-card">
      <h3>Active Today</h3>
      <p class="stat-value">{{ stats.activeToday }}</p>
    </div>
    <div class="stat-card">
      <h3>Avg Session</h3>
      <p class="stat-value">{{ Math.round(stats.averageSession) }}s</p>
    </div>
  </div>
</template>

Output: The stats are fetched once on the server during SSR and hydrated on the client. The unique key ensures that multiple components using the same data don’t cause duplicate network requests.

Middleware

Middleware runs before a route is rendered. Use it for authentication, redirects, or data preloading.

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const token = useCookie('auth-token');
  
  if (!token.value && to.path !== '/login') {
    // Abort navigation and redirect to login
    return navigateTo('/login');
  }
  
  // If no return or undefined, navigation proceeds
});

Apply middleware globally (add _ prefix) or per-page:

<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
  middleware: 'auth'
});
</script>

<template>
  <div>
    <h1>Protected Dashboard</h1>
    <p>Only authenticated users see this.</p>
  </div>
</template>

Output: Visiting /dashboard without a valid auth cookie redirects to /login. With a valid cookie, the dashboard renders normally.

Modules

Nuxt’s modular architecture lets you extend functionality with npm packages:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxtjs/tailwindcss',    // Tailwind CSS
    '@nuxtjs/color-mode',     // Dark/light mode
    '@nuxt/image',            // Optimized images
    '@pinia/nuxt',            // State management
  ],
  
  // Module-specific configuration
  colorMode: {
    classSuffix: '',
    preference: 'system'
  },
  
  image: {
    format: ['webp'],
    screens: {
      xs: 320,
      sm: 640,
      md: 768,
      lg: 1024,
      xl: 1280
    }
  }
});

Output: Modules are loaded automatically and add their features (CSS utilities, image optimization, state management) to the app without manual setup.

Deployment

Nuxt apps deploy as SSR, SSG, or hybrid. Choose based on your needs:

# Static generation (Jamstack) — best for blogs, docs
npx nuxi generate
# Output: .output/public/ — deploy to any static host

# SSR with Node.js server
npx nuxi build
node .output/server/index.mjs
# Deploy to VPS, Cloud Run, or Railway

# Serverless deployment (Vercel, Netlify)
npx nuxi build --preset vercel
# Or set preset in nuxt.config.ts
// nuxt.config.ts — Hybrid rendering
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },                    // Static
    '/blog/**': { ssr: true },                   // SSR
    '/dashboard/**': { ssr: false },              // SPA
    '/api/**': { cors: true, redirect: '/api' }  // API
  }
});

Output: Each route follows its configured rendering strategy. The marketing pages are statically pre-rendered, blog posts use SSR for SEO, and the dashboard is a client-side SPA.

Security Angle: Input Validation in API Routes

// server/api/users.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  
  // Validate input before processing
  if (!body.email || !body.email.includes('@')) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Invalid email address'
    });
  }
  
  // Sanitize string inputs
  const sanitizedName = body.name.replace(/[<>]/g, '');
  
  // Rate limiting check (simplified)
  const ip = getHeader(event, 'x-forwarded-for') || event.node.req.socket.remoteAddress;
  
  // Process validated data
  return { success: true, user: await createUser(sanitizedName, body.email) };
});

DodaTech uses this same validation pattern in Durga Antivirus Pro’s API endpoints to prevent injection attacks when processing user-submitted file metadata.

Common Mistakes Beginners Make

  1. Forgetting await in data fetching: Both useFetch and useAsyncData return promises. Without await, components render before data arrives, causing undefined errors.

  2. Mutating fetched data directly: Never modify data.value directly. Create a reactive copy with ref or reactive before mutating.

  3. Using <script> instead of <script setup>: Nuxt 3’s Composition API works best with <script setup>. The Options API (export default) lacks auto-import benefits.

  4. Not closing the pages/ directory: Any .vue file in pages/ becomes a route. Accidentally leaving draft files there creates unintended URLs.

  5. Mixing SSR and browser APIs: localStorage, window, and document aren’t available during SSR. Wrap browser-only code in onMounted() or use client-only components.

  6. Missing definePageMeta for layout switching: Each page defaults to layouts/default.vue. For different layouts, you must explicitly set definePageMeta({ layout: 'custom' }).

  7. Over-fetching in static generation: useFetch with server: true (default) fetches during SSG build. Ensure API endpoints are reachable at build time or set server: false.

Practice Questions

  1. What is the difference between useFetch and useAsyncData?
  2. How do you create a dynamic route for blog posts with slugs like /blog/my-post?
  3. What does middleware return to redirect a user?
  4. How do you configure a page to render as a client-side SPA instead of SSR?
  5. What happens if you use window in a <script setup> block during SSR?

Answers:

  1. useFetch is a wrapper around useAsyncData that auto-generates keys and handles fetching with options like baseURL and headers. useAsyncData gives more control over the fetch implementation and requires a manual key.
  2. Create pages/blog/[slug].vue. The slug parameter is accessed via useRoute().params.slug.
  3. Return navigateTo('/path') to redirect, or abortNavigation() to cancel navigation entirely.
  4. Add definePageMeta({ ssr: false }) in the page’s <script setup> block.
  5. Nuxt throws an error during SSR because window doesn’t exist on the server. Use onMounted(() => { ... }) to access browser APIs.

Challenge

Build a full-featured blog with Nuxt 3: file-based routes for posts using dynamic slugs, a tags system with filtered views, pagination with useFetch, a search page that queries the API, and middleware that redirects draft posts to a 404 page.

Real-World Task

Create a documentation site (like DodaTech’s) using Nuxt 3 with static generation. Use @nuxt/content for Markdown-based pages, add a sidebar navigation component, implement search with useFetch against a local index, and deploy to Cloudflare Pages or Netlify.

FAQ

What is the difference between Nuxt 2 and Nuxt 3?
: Nuxt 3 uses Vue 3, Vite, and Nitro (a new server engine). It supports TypeScript, auto-imports, hybrid rendering, and is significantly faster. Nuxt 2 is in maintenance mode.
Can I use Nuxt.js without SSR?
: Yes. Set ssr: false in nuxt.config.ts to disable SSR entirely, or use routeRules for per-route configuration. The app then behaves as a standard SPA with Nuxt’s tooling benefits.
How does Nuxt.js handle SEO?
: Nuxt’s SSR and SSG modes render full HTML pages that search engines can crawl. It also provides useHead composable for per-page meta tags, Open Graph data, and structured data.
Does Nuxt support TypeScript?
: Yes, Nuxt 3 has first-class TypeScript support. The configuration file is nuxt.config.ts, and all composables are fully typed with auto-generated type definitions.
What hosting options work with Nuxt?
: Static sites (SSG) deploy to any static host. SSR apps deploy to Node.js servers, serverless platforms (Vercel, Netlify), or Docker containers. Nuxt provides deployment presets for most platforms.

Try It Yourself

# Create a new Nuxt 3 project
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev

Create three pages: index.vue (homepage with hero), about.vue (team section), and blog/[slug].vue (dynamic blog post). Add navigation between them using <NuxtLink>, fetch data on the blog page using useFetch, and apply a custom layout with header and footer.

What’s Next

Related topics: Vue, JavaScript, TypeScript, Node.js

What’s Next

Congratulations on completing this Nuxt.js tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro