Nuxt

Understanding Nuxt.js vs. Vue.js

Nuxt.jsis an open-source framework built on top of Vue.js. While Vue.js is a library focused on the view layer (the UI), Nuxt is a "meta-framework" that provides the architecture, directory structure, and configurations needed to build production-ready applications.

Think of it this way: Vue provides the engine, while Nuxt provides the entire car.


Key Differences at a Glance

The following table highlights the architectural and functional shifts between the two:

Feature Vue.js Nuxt.js
Category Frontend Library / Core Framework Full-stack Meta-framework
Rendering Primarily Client-Side Rendering (CSR) SSR, Static Site Generation (SSG), and CSR
Routing Manual configuration via vue-router File-system based (automatic)
SEO Challenging (requires extra setup) Excellent (built-in SSR/Static support)
State Management Manual setup (Pinia/Vuex) Auto-imported and pre-configured
Folder Structure Flexible / Unopinionated Strict / Opinionated (convention over configuration)

Core Enhancements in Nuxt.js

  1. Rendering Modes: Nuxt allows you to choose how your app is delivered. It supports Rendering (SSR), which sends fully rendered HTML to the browser, and Site Generation (SSG), which pre-builds the site at deployment.
  2. Automatic Routing: You don't need to write a router file. If you create a file named about.vue in the pages/ directory, Nuxt automatically creates the /about route for you.
  3. SEO & Meta Tags: Nuxt provides built-in components and composables (like useHead) to easily manage meta titles, descriptions, and scripts for individual pages, making it superior for search engine visibility.
  4. Auto-imports: Nuxt automatically imports components, composables, and Vue APIs, reducing boilerplate code and keeping your script tags clean.
  5. Nitro Engine: Nuxt uses the Nitro server engine, allowing you to write API routes (server-side code) directly within the same project.

In the context of Nuxt, "Convention over Configuration" is a design philosophy that minimizes the number of decisions a developer needs to make. Instead of manually configuring every aspect of the application (as you would in a raw Vue.js setup), Nuxt assumes "sensible defaults" based on how you name and place your files.

If you follow the established conventions (the folder structure), Nuxt handles the configuration (the underlying code) automatically.


How Convention Replaces Configuration

The following table compares the manual effort required in a standard Vue project versus the automated "convention" approach in Nuxt:

Task Manual Configuration (Standard Vue) Nuxt Convention (Automated)
Routing Import vue-router and manually map paths to components in a router.js file. Create a .vue file in the /pages directory; the route is generated based on the filename.
State Management Install Pinia, create a store, and manually import it into components. Place a file in the /stores directory; Nuxt auto-imports it for use anywhere.
Components Import every component manually: import MyButton from './components/MyButton.vue'. Place components in the /components directory; they are globally available without imports.
Meta Tags / SEO Use a library like vue-meta and configure it for every single route. Use the useHead composable or the app.config.ts for automated SEO handling.
API / Server Code Set up a separate Express/Node server and handle CORS and proxying. Add files to the /server/api directory; Nuxt handles the Nitro server setup automatically.

Key Benefits of This Approach

  • Faster Development (Speed to Market): You spend less time writing "glue code" (boilerplate) and more time writing feature logic.
  • Standardization: Because the structure is opinionated, any Nuxt developer can jump into a project and immediately know where the routes, stores, and components are located.
  • Reduced Decision Fatigue: You don't have to argue over where to put files or how to name them; the framework has already decided the most efficient way.
  • Built-in Optimization: Because Nuxt knows your structure, it can automatically perform code-splitting—only loading the JavaScript necessary for the specific page the user is visiting.

Rendering Modes Comparison

Nuxt 3 is unique because it allows you to mix and match different rendering strategies within a single application using Hybrid Rendering. By default, it uses Universal Rendering, but you can configure specific routes to behave differently.

Rendering Modes Comparison

The table below breaks down the primary ways Nuxt handles content delivery:

Rendering Type When It Renders Best Use Cases Benefits
Universal (SSR) On every request (Server-side) E-commerce, dynamic dashboards High SEO + Live data
Static (SSG) During the build step Blogs, documentation, marketing sites Fastest performance + Cheap hosting
Client-Side (CSR) In the browser (Client-side) Internal tools, SaaS (post-login) Low server load
SWR (Stale-While-Revalidate) On-demand (then cached) Product pages, social feeds Fast cached delivery + Auto-updates
ISR (Incremental Static) On-demand (cached for set time) High-traffic news sites Balance of static speed and dynamic content

Detailed Breakdown

    Universal Rendering (Default):

  • The server generates the HTML and sends it to the browser.
  • Once the browser receives the HTML, it "hydrates" it (downloads JS to make it interactive).
  • Pros:Excellent SEO and fast "First Contentful Paint."
  • Client-Side Rendering (CSR):
  • You can disable SSR by setting ssr: false in nuxt.config.ts
  • The browser receives a nearly empty HTML file and builds the UI using JavaScript.
  • Pros: Ideal for highly interactive apps where SEO is not a priority (e.g., an admin panel).
  • Hybrid Rendering:

    This allows you to define Route Rules in your config file. For example, you can make your homepage static but keep your search results dynamic.

To start a new Nuxt 3 project, you use nuxi, the Nuxt Command Line Interface. It is designed to be lightweight and handles the scaffolding of the project structure automatically.

Quick Start Command

Run the following command in your terminal to initialize a project:

npx nuxi@latest init

Step-by-Step Installation Process

Step Action Command / Note
1. Initialize Run the init command. npx nuxi@latest init my-app
2. Select Package Manager Choose npm, yarn, or pnpm. Use arrow keys to select your preference.
3. Install Dependencies The CLI will ask to install packages. Select Yes to run npm install automatically.
4. Navigate Enter your new project folder. cd my-app
5. Start Development Launch the local dev server. npm run dev -- -o (The -o opens your browser).

Key Prerequisites

  • Node.js: Ensure you have version 18.10.0 or newer installed.
  • Text Editor: Visual Studio Code is recommended, specifically with the Volar extension (configured for Vue 3).
  • Browser: Any modern evergreen browser (Chrome, Firefox, Edge, Safari).

What Happens Behind the Scenes?

When you run the init command, Nuxt:

  1. Clones a minimalstarter template.
  2. Sets up the directory structure(e.g., .nuxt, server, public).
  3. Pre-configures TypeScriptsupport by default.
  4. Prepares the Nitro serverengine for development.

The nuxt.config.ts file is the central configuration hub for a Nuxt 3 application. It allows you to override the default "Convention over Configuration" settings, register modules, and define global behaviors for both the client and the server.Because it is written in TypeScript, it provides full IntelliSense (autocompletion) to help you discover available options as you type

Core Responsibilities of nuxt.config.ts

Category Purpose Example Usage
App Head Global SEO and Metadata Setting the default <title>, meta tags, and external scripts.
Modules Extending Functionality Registering tools like @nuxtjs/tailwindcss, @pinia/nuxt, or @nuxt/content.
Route Rules Rendering Strategies Defining which pages are Static (SSG), Server-rendered (SSR), or Cached (SWR).
Runtime Config Environment Variables Managing private keys (server-side only) and public keys (client-side).
CSS/Plugins Global Assets Importing global CSS files or registering custom Nuxt plugins.
Nitro Server Backend Config Configuring the underlying server engine for deployments (e.g., Vercel, Netlify).

Key Features and Structure

  • defineNuxtConfig Wrapper:This helper function ensures your configuration is type-checked.
  • Runtime Config: Nuxt distinguishes between public keys (accessible in the browser) and private keys (accessible only by the Nitro server), preventing sensitive data leaks.
  • Auto-updates: In development mode, Nuxt watches this file; most changes will trigger an automatic restart of the development server to apply the new settings.

Code Example: A Typical Config

In Nuxt, the In Nuxt, the means your application's URL structure is defined by the physical files and folders within the pages/ directory. Nuxt uses the Nitro engine to scan this directory and automatically generate a router configuration using vue-router under the hood.

This removes the need to manually maintain a router.js file.


Mapping Files to URLs

The naming convention of your files directly determines the path. Here is how common routing patterns are translated:

File Path Resulting URL Route Type of Route
pages/index.vue / Home Page
pages/about.vue /about Static Route
pages/users/index.vue /users Nested Directory Route
pages/users/[id].vue /users/123, /users/abc Dynamic Route
pages/blog/[...slug].vue /blog/my/long/path Catch-all Route
pages/404.vue /404 Custom Error Page

Key Routing Concepts

  • Dynamic Routes([id].vue): Use square brackets to define a parameter. You can access this in your component using useRoute().params.id.
  • Catch-all Routes ([...slug].vue): The ellipsis allows the route to match multiple path segments (e.g., /blog/2026/02/news).
  • Nested Routes: If you create a folder and a.vue file with the same name (e.g., parent.vue and parent/child.vue) , Nuxt will treat the child as a nested view inside a component within the parent.
  • Navigation: Instead of standard a tags, Nuxt provides the component. This enables smart pre-fetching (loading the next page's data before the user even clicks) and faster client-side transitions.
  • Mount Service: Handles SD cards and internal storage mounting.
  • Telephony Registry: Monitors signal strength and call status.

Validation and Middleware

You can also add a definePageMeta macro inside your page files to:

  • Add Middleware (e.g., checking if a user is logged in before showing the page).
  • Set a specific Layout
  • Validate route parameters (e.g., ensuring an ID is a number).

The .nuxt directory is the generated output of the Nuxt development environment. It serves as the "brain" of your project while it is running in development mode or being prepared for production.

You should generally never manually edit files inside this folder, as they are overwritten every time you run npm run dev or npm run build.


Key Functions of the .nuxt Directory

Function Description
Code Generation Converts your Vue files, layouts, and components into the final JavaScript that the browser and server understand.
Route Mapping Stores the generated routes.js file created by the file-based routing system.
Auto-Imports Maintains the "virtual" files that allow you to use components and composables without explicit import statements.
Type Definitions Generates TypeScript declaration files (.d.ts) so your IDE (like VS Code) provides accurate autocompletion.
App Entry Point Contains the main client-side and server-side entry points that initialize the Vue application.

Crucial Facts for Developers

  • Git Ignore:This directory is automatically added to your .gitignore. You should not commit it to version control because its contents are specific to the local machine and the current build.
  • Virtual File System: Nuxt uses a virtual file system (VFS) to bridge the gap between your source code and the running application. The .nuxt folder is the physical manifestation of that VFS.
  • Troubleshooting: If you encounter strange "module not found" errors or TypeScript mismatches, a common fix is to delete the .nuxt folder and restart your development server. This forces Nuxt to regenerate everything from scratch.
  • Types Generation: You can manually trigger the generation of these files (specifically for IDE support) by running:

In Nuxt, dynamic routes are created using square bracket syntax [] within your file or folder names in the pages/ directory. This tells Nuxt that the segment of the URL is a variable (parameter) rather than a static string.

Creating the File Structure

To create a route like /user/123, you would structure your directory as follows:

  • File Path: pages/user/[id].vue
  • Resulting URL: /user/:id (where :id can be anything like 123, abc or john-doe).

Accessing the Route Parameter

Once the file is created, you need to extract the id to use it in your logic (e.g., fetching user data from an API). You do this using the useRoute() composable.

Approach Code Example
Inside <script setup> const route = useRoute();
console.log(route.params.id);
Inside <template> {{ $route.params.id }}

Advanced Dynamic Routing Patterns

Nuxt supports more complex scenarios beyond simple IDs:

  • Multiple Parameters: pages/posts/[category]/[id].vue maps to /posts/tech/123
  • Catch-all Routes: pages/docs/[...slug].vue maps to /docs/intro.
  • Optional Parameters: pages/user/[[id]].vue (double brackets) makes the id optional, meaning it matches both /user and /user/123.

Parameter Validation

You can use the definePageMeta macro to ensure the dynamic parameter meets specific criteria (e.g., ensuring an ID is a number). If validation fails, Nuxt will automatically return a 404 error.

While both directories contain .vue files, they serve distinct architectural purposes in a Nuxt application. The primary difference lies in routing and lifecycle.

Core Differences

The following table summarizes the structural and functional divergence between the two:

Feature pages/ components/
Primary Role Defines the application's Routes (URLs). Defines Reusable UI elements.
Routing Automatically generates a URL (e.g., about.vue/about). No URL; must be imported or used inside a page.
Auto-import Handled by the Router. Handled by Nuxt’s auto-import system.
Data Fetching Typically where top-level API calls happen. Receives data via props from pages.
Special Macros Supports definePageMeta (middleware, layouts). Does not support page-level metadata macros.

Detailed Roles

The pages/ Directory Directory

  • Entry Points: Every file here is an entry point for a user.
  • Orchestration: Pages act as "containers." They fetch data (using useFetch or useAsyncData) and then pass that data down to various components.
  • Layouts & Middleware: This is where you define which layout a page uses or which middleware should run before the user views the content.

The components/ components/

  • Modularity: This is for "bricks" like AppButton.vue, TheNavbar.vue, or UserProfileCard.vue.
  • Organized Subfolders: If you have components/base/Button.vue, Nuxt allows you to use it as automatically.
  • Lazy Loading: You can prefix a component with Lazy (e.g., ) to delay downloading its code until it’s actually needed on the screen.

Visualizing the Relationship

  • A Page is a specific destination (The "Profile" Page).
  • A Component is a piece of that destination (The "Avatar" image or the "Follow" button).

Nuxt features a powerful Auto-import system that eliminates the need to manually write import statements for your internal files or the Vue/Nuxt APIs.


How Auto-imports Work

Nuxt scans specific directories during the development process and generates a Virtual File System (stored in .nuxt/imports.d.ts) ) that makes these functions and components globally available to your project.

Directory / Source What is Auto-imported? Example Usage
components/ All .vue files in this folder. <MyButton />
composables/ Named or default exports from files. const { data } = useMyApi()
utils/ Helper functions and logic. formatDate(new Date())
Vue & Nuxt APIs Core functions like ref, computed, useRoute. const count = ref(0)

Specific Rules for Components

  • Nested Folders: If you have components/base/Button.vue , the component name is derived from the path: .
  • Lazy Loading: You can skip the initial JavaScript bundle for a component by prefixing it with Lazy. For example, using will only download the component's code when the modal is actually rendered.
  • Direct Imports: If you prefer manual imports (e.g., for complex IDE refactoring), you can still import them normally; Nuxt will not conflict with manual imports.

Specific Rules for Composables

  • Top-level only Nuxt only scans the top-level files in the composables/ directory (e.g., composables/useAuth.ts).
  • Nested Composables: If you want to organize composables in subfolders, you must either export them from an index.ts in the root of the folder or configure the nuxt.config.ts to scan those subfolders.

Developer Experience (IDE Support)

s Because Nuxt generates TypeScript declaration files in the .nuxt folder, your IDE (like VS Code with Volar) will still provide:

  • Autocompletion: Suggestions as you type the component or function name.
  • Type Checking:Warnings if you pass the wrong props to an auto-imported component.
  • Go to Definition: Clicking the component name will still take you to the source file.s

Universal Rendering (SSR) in Nuxt

Universal Rendering (also known as Server-Side Rendering or SSR) is a technique where the server executes the JavaScript code and generates the full HTML of a page before sending it to the browser.

Once the browser receives the HTML, it displays it immediately. It then downloads the JavaScript to "hydrate" the page, making it interactive (connecting event listeners, state, etc.).


Universal Rendering vs. Client-Side Rendering

Feature Universal Rendering (SSR) Client-Side Rendering (CSR)
First Contentful Paint Fast: User sees the page content almost instantly. Slow: User sees a blank screen while JS downloads.
SEO Excellent: Search engine crawlers see full HTML content. Challenging: Crawlers may struggle to execute JS.
Server Load Higher: Server must process every request. Lower: Server just sends static files.
Initial Bundle Size Smaller initial HTML payload. Large initial JS bundle required to start.

Why is it the Default?

Nuxt chooses Universal Rendering as the default because it provides the best balance of user experience and technical performance for modern web applications.

  • Optimized SEO: Since the server sends a fully rendered page, search engines (Google, Bing) and social media bots (Twitter, Open Graph) can index your content accurately without needing to execute complex JavaScript.
  • Perceived Performance: Users on slower devices or unstable mobile connections see the content much faster. They don't have to wait for a heavy JavaScript framework to initialize before reading the text.
  • The "Best of Both Worlds": Nuxt doesn't just give you a static page. After the initial fast load, the app becomes a Page Application (SPA). This means subsequent navigations are nearly instantaneous because only the necessary data is fetched, not the whole page.

The Hydration Process

This multi-step process ensures that users see content immediately while still receiving a fully interactive experience:

  • Request: The user hits the URL in their browser.
  • Server: Nuxt renders the Vue components into full HTML strings on the server side.
  • Response: The browser receives the pre-rendered HTML and displays the UI immediately (First Contentful Paint).
  • Hydration: The browser downloads the Vue.js bundle and "takes over" the static HTML, turning it into a live, reactive application that can respond to user clicks and state changes.

To switch a Nuxt application to Single Page Application (SPA) mode, you effectively disable Server-Side Rendering (SSR) In this mode, the server sends an empty HTML shell with a tag, and the browser handles all the rendering, just like a standard Vue.js CLI or Vite project.


Method 1: Global SPA Mode

If you want the entire application to behave as an SPA, modify your nuxt.config.ts file by setting the ssr property to false.

What happens next:
  • The server will no longer execute your Vue code.
  • The application will be purely client-side.
  • SEO will be limited as crawlers will see a mostly empty HTML file initially.

Method 2: Hybrid Rendering (Per-Route SPA)

Nuxt 3 allows you to keep the benefits of SSR for most of your site while making specific sections (like an admin dashboard) behave as an SPA. This is done via routeRules.

Use Case Configuration Example
Entire Site as SPA ssr: false in root config.
Specific Folder (e.g., /admin) routeRules: { '/admin/**': { ssr: false } }
Single Page (e.g., /dashboard) routeRules: { '/dashboard': { ssr: false } }

Key Considerations when Switching to SPA

Before disabling Universal Rendering, keep these technical shifts in mind to ensure your application functions as expected:

  • The <ClientOnly> Component: In SPA mode, you don't strictly need this component, but it remains useful in SSR mode to wrap parts of a page that should only run in the browser (like a complex chart or a map).
  • Data Fetching: In SPA mode, useFetch and useAsyncData will only run on the client. You won't see the "server-side" network call in the Nuxt logs; it will appear in the browser's Network tab instead.
  • Deployment: When running npm run build for a global SPA, Nuxt will generate a static entry point (usually index.html) that can be hosted on any static web server (S3, Vercel, Netlify) without needing a Node.js environment.

Static Site Generation (SSG) is a rendering mode where Nuxt pre-renders your entire application into static HTML, CSS, and JavaScript files during the build process Instead of generating pages on-demand when a user visits (like SSR), the work is done upfront.

The resulting files are "flat" and can be hosted on any static web server or Content Delivery Network (CDN) without requiring a Node.js server.


How SSG Works in Nuxt

Static Site Generation (SSG) combines the SEO benefits of SSR with the low-cost hosting of static files:

  • Build Phase: When you run nuxi generate, Nuxt crawls your routes and executes the logic for every page.
  • Payload Generation: For every route (e.g., /about), Nuxt creates an index.html file and a small JSON "payload" containing the data fetched during the build.
  • Deployment: You upload the .output/public folder to a host like Netlify, Vercel, or GitHub Pages.
  • Client-Side Navigation: Once the first page loads, the app "hydrates" into a Single Page Application (SPA), making subsequent clicks instant.

SSG vs. Other Rendering Modes

Feature Static Site Generation (SSG) Server-Side Rendering (SSR)
Server Required No (Static Hosting) Yes (Node.js Environment)
Build Time Longer (proportional to page count) Fast
Page Load Speed Fastest (served from CDN edge) Fast (depends on server speed)
Data Freshness At time of build Real-time
Best For Blogs, Documentation, Portfolios Dashboards, E-commerce, Social Media

Key Nuxt Commands for SSG

To effectively generate a static site, you need to use the specific build commands and understand how Nuxt discovers your routes:

  • npx nuxi generate: This is the primary command for SSG. It builds the application and then exports it to static files.
  • Dynamic Routes in SSG: For routes like /blog/[slug], Nuxt needs to know which slugs exist to generate the pages. You can provide these in nuxt.config.ts or let Nuxt crawl your <NuxtLink> tags to find them automatically.

The "Hybrid" Advantage

In Nuxt 3, you aren't forced to choose SSG for the whole site. Using Route Rules, you can make your landing pages static (SSG) for speed, while keeping your account settings page as a Single Page App (CSR).

Route Rules are the engine behind Hybrid Rendering in Nuxt 3. They allow you to define different caching and rendering strategies for different sections of your application within a single configuration file.

Instead of choosing one rendering mode for your entire app, you can assign rules to specific URL patterns (e.g.,/blog/** vs. /admin/**).


Core Rendering Strategies in Route Rules

The following table explains the different "behaviors" you can apply to routes:

Feature Static Site Generation (SSG) Server-Side Rendering (SSR)
Server Required No (Static Hosting) Yes (Node.js Environment)
Build Time Longer (proportional to page count) Fast
Page Load Speed Fastest (served from CDN edge) Fast (depends on server speed)
Data Freshness At time of build Real-time
Best For Blogs, Documentation, Portfolios Dashboards, E-commerce, Social Media

How it Works: The Nitro Engine

Route Rules are processed by Nitro, Nuxt’s server engine. When a request comes in, Nitro checks the defined rules and decides whether to:

  • Serve a pre-generated static file: Fast delivery from disk or CDN.
  • Execute a server-side function: Renders the page fresh for every request (SSR).
  • Serve a "stale" (cached) version: Delivers cached content while fetching new data in the background (SWR/ISR).

Example Configuration

In your nuxt.config.ts, you can mix these strategies to optimize performance and SEO:

Why use Route Rules?

Route Rules provide the flexibility to optimize different parts of your application based on their specific requirements:

  • Granular Control: You don't sacrifice SEO on your landing pages just because your dashboard needs to be an SPA.
  • Cost Efficiency: SWR and ISR reduce server load by serving cached versions of dynamic pages.
  • Global Deployment: Nitro optimizes these rules for the platform you are deploying to (Vercel, Netlify, Cloudflare, etc.).

Stale-While-Revalidate (SWR) is a caching strategy that allows Nuxt to serve a page instantly from a cache (the "stale" version) while simultaneously triggering a background re-generation of that page (the "revalidate" step) to ensure the next visitor sees updated content.

It provides the speed of a static site with the flexibility of a dynamic site.

The SWR Lifecycle

When a user requests a route configured with SWR, the following process occurs:

Step Action Result
1. Initial Request User hits a URL for the first time. The server renders the page (SSR) and stores it in the cache.
2. Subsequent Request Another user hits the same URL. The server instantly serves the cached HTML (even if it's old).
3. Background Refresh Nuxt detects the cache is "stale." Nitro triggers a background re-render to update the cache.
4. Future Request A later user hits the URL. They receive the newly updated version from the previous background refresh.

Configuring SWR in Nuxt

You enable SWR using routeRules in your nuxt.config.ts. You can set it to true (infinite cache until a server restart) or a specific number of seconds.


Key Benefits

Implementing SWR (Stale-While-Revalidate) and ISR (Incremental Static Regeneration) provides significant advantages for high-traffic applications:

  • Zero Latency: Users never wait for the server to fetch data or render HTML because they are always served from the cache.
  • Reduced Server Load: The server only renders the page once per "stale" period, regardless of how many thousands of users visit.
  • High Availability: If your data source (API) goes down, Nuxt will continue to serve the "stale" cached version rather than showing an error page.

SWR vs. ISR (Incremental Static Regeneration)

While very similar, the distinction in the Nuxt/Nitro ecosystem is:

  • SWR: Typically focused on caching at the Edge (CDN level). It serves the stale version immediately and refreshes the content in the background for the next visitor.
  • ISR: Often implies the ability to revalidate on a schedule or via a manual trigger (webhook), though in many modern hosting providers, these terms are used interchangeably.

In Nuxt 3, the useHead composable is the primary tool for managing SEO and document metadata (like title, meta, link tags) programmatically. It works both on the server and the client, ensuring that search engines see your metadata in the initial HTML.

Basic Usage of usehead

You call usehead inside the block of your page or component. It accepts an object where keys correspond to HTML head elements.


Dynamic and Reactive Metadata

Because usehead is built on top of the Unhead library, it can accept computed properties or refs. If your data changes (e.g., a product name is loaded from an API), the head tags will update automatically in the browser.


Alternative: useSeoMeta

For standard SEO tasks, Nuxt provides a more concise composable called useSeoMeta.It offers full TypeScript support for common tags, reducing the chance of typos in property names like og:description.

Best Practices for SEO in Nuxt

To maximize search engine visibility and maintain clean meta tags, follow these core implementation strategies:

  • Placement: Use useHead in the pages/ directory for page-specific SEO. Use it in app.vue or a layout for global defaults.
  • Server-Side Rendering: Always ensure SSR is enabled for pages where SEO is critical, as useHead ensures the tags are present in the initial HTML delivered to crawlers.
  • Avoid Duplication: Nuxt automatically merges head tags. If you define a title in app.vue and another in about.vue, the page-specific one will override the global one.

While both macros/composables are used within the pages/ directory, they serve entirely different layers of the application. useSeoMeta handles what the browser and search engines see (HTML head), while definePageMeta handles how Nuxt treats the page internally (routing and logic).


Key Differences

Feature useSeoMeta definePageMeta
Primary Purpose SEO and Social Media Metadata. Page configuration and Routing.
Output Location The <head> section of the HTML. Internal Nuxt/Vue Router state.
Reactivity Fully Reactive: Updates if data changes. Static: Evaluated at build/compile time.
Standard Tags Title, Description, OpenGraph, Twitter. Layouts, Middleware, Transitions, Validation.
Scope Can be used in Pages and Components. Only usable inside Files in the pages/ directory.

Detailed Breakdown

useSeoMeta (The "External" Face)

This is a helper for SEO. It maps objects to HTML tags. It is preferred over useHead for SEO because it provides TypeScript autocomplete for over 100 social meta tags (like ogTitle, twitterCard).

Example: definePageMeta (The "Internal" Brain)

This is a compiler macro. It tells Nuxt how to handle the page within the framework's ecosystem. It cannot access component state (like ref) because it is processed before the component is even instantiated.

Example:

Summary of Usage

Understanding when to use each composable is key to managing your Nuxt 3 application effectively:

  • Use useSeoMeta: When you want to change what shows up on a Google Search result or a shared link on social media platforms like Twitter.
  • Use definePageMeta: When you want to change which layout the page uses, protect the route with a password (middleware), or validate if a URL parameter is a number.

Implementing a sitemap in Nuxt 3 is most efficiently done using the nuxt-simple-sitemap module (now part of the Nuxt SEO suite). It automatically generates sitemap.xmlfiles based on your page structure and supports dynamic routes.

Method 1: Automatic Generation (Recommended)

For most projects, you want a sitemap that stays in sync with your pages/ directory automatically.

Step Action Command/Code
1. Install Add the module to your project. npm install -D @nuxtjs/sitemap
2. Register Add it to your nuxt.config.ts. modules: ['@nuxtjs/sitemap']
3. Configure Set your site URL (required). site: { url: 'https://example.com' }

Handling Dynamic Routes

Since Nuxt cannot "guess" all your database-driven IDs or slugs (e.g., /products/[id]), you must provide them so they appear in the sitemap:

  • Static SSG: If you use npx nuxi generate, the crawler will often find links to these pages and add them automatically.
  • Dynamic/SSR: You can provide a function or an endpoint in nuxt.config.ts to fetch these IDs:

Key Features of Nuxt Sitemap

The @nuxtjs/sitemap module automates complex SEO requirements, ensuring your site remains compliant with search engine standards:

  • Multi-Sitemap Support: Automatically splits your sitemaps if you have more than 50,000 URLs (SEO best practice).
  • I18n Integration: If you use @nuxtjs/i18n, the sitemap automatically generates <xhtml:link> tags for different language versions of the same page.
  • Automated Metadata: It uses the last modified date of the files to populate the <lastmod> tag.
  • Route Exclusion: You can easily hide pages (like /admin or secret routes) from the sitemap.

Method 2: Manual (For Simple Sites)

If you prefer not to use a module, you can create a Server Route at server/routes/sitemap.xml.ts. You would then manually write the XML string and set the Content-Type header to header to. However, this requires manual maintenance as you add new pages.

Yes, Nuxt supports automatic image optimization primarily through the official Nuxt Image module (@nuxt/image). It is designed to handle the heavy lifting of resizing, compressing, and serving images in modern formats like WebP or AVIF.

How Nuxt Image Works

By replacing the standard HTML tag with the or components, Nuxt automatically optimizes the asset based on the user's device and the provider you choose (e.g., local server, Cloudinary, Vercel).


Core Components Comparison

Component Use Case Best Feature
<NuxtImg> Standard images. Generates a single optimized URL with specific width/height.
<NuxtPicture> High-performance images. Uses the <picture> tag to serve different formats (e.g., AVIF) based on browser support.

Key Features & Optimization Techniques

The @nuxt/image module provides powerful tools to automate image delivery and significantly improve Core Web Vitals:

  • Format Conversion: Automatically converts PNG/JPG to smaller, high-quality formats like WebP or AVIF.
  • Responsive Sizes: You can define a sizes attribute (e.g., sm:100vw md:50vw lg:400px), and Nuxt will generate multiple versions of the image for different screen widths.
  • Lazy Loading: Images are natively lazy-loaded by default, meaning they only download when they are about to enter the viewport.
  • Placeholders: Supports low-quality image placeholders (LQIP) or blurred effects while the main image loads, improving perceived performance.
  • Providers: Includes built-in support for various transformation services:
    • IPX (Default): A local image processor based on Sharp.
    • External: Cloudinary, Fastly, Imgix, Vercel, and AWS.

Basic Usage Example


Implementation Steps

  • Install: Run npm install @nuxt/image
  • Register:Add '@nuxt/image' to the modules array in nuxt.config.ts.
  • Use:Replace tags with .

Nuxt is designed as a "performance-first" framework. It addresses Core Web Vitals (CWV) by automating many of the technical optimizations that would otherwise require manual configuration in a standard Vue.js or React application.


Impact on Core Web Vitals

Metric Focus How Nuxt Improves It
LCP (Largest Contentful Paint) Loading Speed SSR/SSG delivers HTML immediately. The Nuxt Image module prioritizes "above-the-fold" images and converts them to WebP/AVIF.
CLS (Cumulative Layout Shift) Visual Stability Automatic font optimization (Nuxt Fonts) and mandatory width/height attributes in Nuxt Image prevent elements from jumping.
INP (Interaction to Next Paint) Responsiveness Code splitting and smart pre-fetching ensure the browser isn't bogged down by heavy JS, keeping the main thread free for user input.

Key Built-in Optimizations

1. Smart Pre-fetching

Nuxt automatically observes links in the viewport (using the Intersection Observer API). When a becomes visible, Nuxt pre-fetches the JavaScript and data for that page in the background. By the time the user clicks, the transition feels instantaneous.

2. Zero-Config Font Optimization (@nuxt/fonts)

Standard web fonts often cause layout shifts (CLS) while loading. Nuxt Fonts:

  • Automatically downloads and hosts Google Fonts locally.
  • Generates fallback font metrics so the "blank" space matches the final font size, eliminating jumps.
3. Route-Based Code Splitting

Nuxt splits your application into small chunks. If a user visits /home, the browser only downloads the JavaScript required for the homepage. This keeps the "Initial JavaScript Execution" time low, directly benefiting INP.

4. Minimal Hydration

Nuxt 3 uses an optimized hydration process. You can use the component or Lazy Components to prevent heavy, non-essential parts of the page (like a footer or a complex modal) from delaying the initial interactive state.

The Role of Nitro

The underlying server engine (Nitro) is extremely lightweight. It yields a very low TTFB (Time to First Byte), which is the foundation for a good LCP score. Whether you are on a CDN or a VPS, Nitro ensures the server responds as quickly as possible.

In Nuxt 3, both useFetch and useAsyncData are designed to handle data fetching while preventing "double-fetching" (where the server fetches data and then the client fetches it again during hydration).

The simplest way to think about them is: useFetch a high-level "shortcut," while useAsyncData the low-level "engine."


Core Comparison Table
Feature useFetch useAsyncData
Primary Input A URL string (e.g., /api/data). A Handler function (e.g., () => ...).
Best For Standard REST/API requests. Complex logic, SDKs (Firebase/Supabase), or multiple parallel calls.
Implementation Wrapper around useAsyncData + $fetch. Generic wrapper for any async logic.
Auto-Keying Generates a key based on the URL. Generates a key based on file name/line number (manual key recommended).
Simplicity High: One line for most API calls. Medium: Requires a wrapper function.

When to Use Which?

Use useFetchfor Simple API Calls

If you just need to get data from a specific endpoint, useFetch is the most efficient choice. It automatically handles the URL, reactivity, and type inference.

UseuseAsyncData for Complex Logic

Use this when your data fetching involves more than just a single URL request. Common scenarios include:

  • Multiple Requests: Fetching from two APIs and merging the results
  • External SDKs:Using a library like cms.getEntries() or db.collection().get()
  • Custom Transformations: Running heavy logic on the data before it ever reaches your component state.

A Common Mistake: $fetch alone

Many developers accidentally use $fetch (Nuxt's underlying HTTP client) inside their components without a wrapper.

  • The Problem: $fetch does not save its state to the Nuxt payload. This causes the data to be fetched on the server, discarded, and then fetched again on the client.
  • The Fix: Always wrap $fetch in useAsyncData if it is being called during the initial page load.

Return Values

Both composables return the exact same object structure:

  • data: The result of the async function.
  • pending: A boolean indicating if the data is still loading.
  • refresh / execute: Functions to manually trigger a re-fetch.
  • error: An error object if the request failed.
  • status: A string (idle,pending, success, or error).

Choosing between$fetch and useFetch is less about "what works" and more about "where it's running." In Nuxt, the key difference is SSR safety and payload transfer.

The Gold Standard Comparison
Feature useFetch (Composable) $fetch (Utility)
Best Used In <script setup> or middleware. Event handlers (clicks, submits).
SSR Behavior Safe: Fetches once on server, transfers data to client via payload. Unsafe: Can trigger a "double fetch" (once on server, once on client).
Reactivity Automatic: Returns reactive data, status, and error refs. Manual: Returns a raw Promise; you must manage state yourself.
Lifecycle Tied to the component lifecycle. Independent; can be used anywhere (utils, etc.).

When to Use $fetch

You should reach for $fetch when the request is event-driven and happens strictly on the client after the page has already loaded:

  • Form Submissions: Sending a POST or PUT request when a user clicks "Submit."
  • User Interactions: Triggering an API call on a button click (e.g., "Like" a post or "Add to Cart").
  • Inside Logic/Utils: When writing a helper function that isn't a Vue component but needs to make a network request.
  • API Routes: When writing your own server-side API endpoints in server/api/, you use $fetch to call other external APIs.

Why Avoid $fetch in <script setup>?

If you use await $fetch() directly in the body of your <script setup>, it triggers the following sequence:

  • On the Server: Nuxt fetches the data to render the initial HTML.
  • On the Client: During hydration, the browser executes the same script. Since $fetch doesn't save its result to the Nuxt "payload," the browser calls the API again.
  • On The Result: Your server is hit twice, which can lead to "hydration mismatches" where the client data flashes or overwrites the server data.

Summary: Rule of Thumb

  • Initial Page Data? Use useFetch
  • User Clicked a Button? Use $fetch.
  • Complex Logic + SSR? Use useAsyncData(() => $fetch(...)).

To handle API errors globally in Nuxt 3, you should use interceptors within a custom fetch instance. Nuxt uses ofetch under the hood, which provides lifecycle hooks like onResponseError to catch errors across your entire app.

The most robust pattern is to create a custom fetch composable that wraps your logic in one place.


Step 1: Create a Custom Fetch Plugin

First, create a plugin to define a global API instance with interceptors. This is where you handle specific status codes (like 401 Unauthorized).


Step 2: Create a Custom Composable

To make this instance easy to use with Nuxt's SSR-friendly useFetch , wrap it in a composable.


Comparison of Error Handling Methods
Method Best For Behavior
Interceptors (onResponseError) Global Actions Best for redirects (401), logging, or refreshing tokens.
NuxtErrorBoundary UI Isolation Wraps components to show a fallback UI if a child fails.
error.vue Fatal Errors A top-level page that catches unhandled 404s or 500s.
createError Manual Triggers Used to intentionally throw an error that Nuxt can catch.

Bonus: The NuxtErrorBoundary Component

If you want to prevent a single component's API failure from breaking the entire page, use the built-in boundary component.

useState is a Nuxt-specific composable used for creating SSR-friendly reactive state. While it looks and behaves similarly to a standard Vue ref, it is specifically designed to solve the challenges of hydration and cross-request state pollution in a server-side rendered environment.


Key Differences: useState vs. Standard ref

Feature Standard Vue ref Nuxt useState
Persistence Lost during hydration; resets to initial value on the client. Preserved: Value is serialized on the server and "hydrated" on the client.
Scope Local to the component (unless defined globally). Shared: Key-based system allows access across any component.
SSR Safety Risk of State Leakage if defined at the module level. Safe: Isolated per user request on the server.
Identification No key required. Requires a Unique Key to identify the state piece.

Why You Need useState for SSR

1. Preventing Hydration Mismatches

If you use ref(Math.random()) in a component, the server will generate one number, and the client will generate a different one during hydration. This causes a "Hydration Mismatch" error. useState ensures the client picks up the exact same value the server generated.

2. Avoiding State Leakage

In a standard Vue SPA, a global ref is fine because each user has their own browser. However, on a Nuxt server, a ref defined outside a component becomes a singleton shared by every user visiting the site. This could lead to User A seeing User B's private data. useState prevents this by scoping state to the specific request.


When to Use Each

  • Use ref: For local, temporary UI state that doesn't need to be shared or persisted from the server (e.g., aisMenuOpen toggle).
  • Use useState: For any state that needs to be shared across components or must be identical on both server and client (e.g., user authentication status, theme preferences).
Code Example

Yes, Pinia is the officially recommended state management library for Nuxt 3. While Nuxt provides useState for simple shared state, Pinia is preferred for large-scale applications that require organized, complex logic and developer tools support.


Pinia vs. useState
Feature useState Pinia
Complexity Simple, key-value pairs. Structured (State, Actions, Getters).
DevTools Basic support via Nuxt DevTools. Excellent: Dedicated timeline and inspection.
Boilerplate Very low. Moderate (requires store definitions).
Persistence Manual implementation needed. Easy via plugins (e.g., pinia-plugin-persistedstate).
Best For Theme toggles, simple flags. Auth, Carts, complex Data Dashboards.

Setting Up Pinia in Nuxt

Nuxt makes integration seamless through the @pinia/nuxtmodule.

Installation: npm install pinia @pinia/nuxt

Configuration: Add it to you modules in nuxt.config.ts.

Usage: Define a store in the stores/ directory (Nuxt will auto-import it).


Crucial Rule: SSR Safety

When using Pinia with Nuxt (SSR), you must follow two rules to avoid State Leakage (where one user sees another user's data):

  • Avoid Global Stores:Never define a store instance outside of a function. Always use defineStore.
  • Use the Composition API:Inside your components, always call the store hook inside setup or .

Why use Pinia in Nuxt?

  • Server-to-Client Transfer: Like useState, Pinia handles the serialization of state. The server fetches the data, populates the store, and the client "hydrates" that exact state without re-fetching.
  • Modularization: It allows you to separate your business logic from your Vue components.
  • HMR (Hot Module Replacement) You can edit your stores without reloading the entire page or losing the current state.

A Hydration Mismatch occurs when the DOM structure or data rendered on the server does not exactly match the DOM structure or data generated by the client during its first render.

Nuxt detects this discrepancy and "bail out" of hydration, which can lead to broken event listeners, flickering UI, or slower performance.


Common Causes and Fixes

Cause Example Solution
Non-Deterministic Data ref(Math.random()) or new Date() Use useState to sync the server-generated value to the client.
Invalid HTML Nesting <p> <div>Content</div> </p> Ensure your HTML is semantically valid (e.g., no div inside p).
Browser-Only APIs window.innerWidth or localStorage Wrap code in onMounted or use the <ClientOnly> component.
Third-Party Plugins Chart libraries or Google Maps Ensure they are initialized only on the client side.

Strategic Solutions

1. Use useState for Consistency

If you need a random ID or a timestamp that remains identical on both sides, useState is mandatory. It serializes the value into the HTML payload so the client doesn't re-calculate it.

2. Use the Component

For components that rely heavily on browser-specific features (like a complex data table or a window-width listener), wrap them in . You can also provide a placeholder to show during SSR to prevent layout shifts.

3. The onMounted hook

Logic inside Logic insideonly runs in the browser. Use this to modify state that depends on window or document


How to Debug Mismatches

  • Nuxt DevTools: Open the DevTools in your browser; it often highlights exactly which element caused the hydration error.
  • Console Logs: Chrome will typically log: "Hydration completed but contains mismatches." It will then list the expected vs. actual HTML.
  • View Source: Right-click and "View Page Source." Compare that static HTML with what you see in the "Elements" tab of your inspector.

The "Last Resort" Attribute

If you have a specific element that you know will be different (and you're okay with it), you can use the data-hydration-uid or the Vue-specific suppressHydrationWarnin attribute, though this should be avoided if possible.

In Nuxt 3, useFetch (and useAsyncData) returns a set of helper functions specifically designed for manual re-execution. The primary method for this is the refresh function (also aliased as execute).


Ways to Trigger a Refresh

There are three main ways to re-trigger a data fetch: using the returned function, using a reactive dependency, or using a global utility.

Method Trigger Type Best Use Case
refresh() Manual/Imperative Button clicks, form submissions, or interval timers.
watch (Option) Reactive Re-fetching data when a URL parameter or ID changes.
refreshNuxtData() Global Refreshing data in one component from a completely different component.

1. Manual Refresh (The Function Call)

When you call useFetch, destructure the refresh function. You can then call this inside any event handler.


2. Automatic Refresh (Reactive Watch)

If your API call depends on a reactive variable (like a page number or search query), use the watch option. When the variable changes, useFetch automatically re-executes.


3. Global Refresh (refreshNuxtData)

Sometimes you need to refresh data in a parent component after an action in a child component (like refreshing a list after deleting an item). You can target a specific useFetch call using its unique key.


Key Considerations

  • refresh vs. execute: These are essentially the same. execute is often used when { immediate: false } is set in the options, meaning the fetch doesn't run until you manually call it.
  • Pending State: The pending ref returned by useFetch will switch back to true every time refresh is called, allowing you to show loading spinners during updates.
  • Data Deduplication: If multiple components are listening to the same key, calling refreshNuxtData will update all of them simultaneously with a single network request.

In Nuxt 3, the "Lazy" version of data fetching allows your application to navigate to a new page immediately, without waiting for the data to finish loading on the client side.

By default, useFetch blocks the navigation (the browser "hangs" for a split second) until the data is resolved. useLazyFetch breaks this block, making the UI feel more responsive.


useFetch vs useLazyFetch

Feature useFetch useLazyFetch
Navigation Blocking: Wait for data before showing the page. Non-blocking: Show the page immediately.
User Experience Best for critical data (SEO/Meta tags). Best for "below-the-fold" or secondary data.
Pending State Usually starts as false on the client. Starts as true on the client.
Implementation Equivalent to useFetch(url). Equivalent to useFetch(url, { lazy: true }).

How it Works in Practice

When you use useLazyFetch, you must handle the "loading" state in your template, otherwise your app might try to render data that doesn't exist yet, leading to errors.


Why Use Lazy Fetching?

  • Perceived Speed: Users see the layout and navigation elements immediately, which feels faster than staring at a loading bar on the previous page.
  • Hybrid Importance: If you have a sidebar that loads data from a slow API, using useLazyFetch ensures the main content of your page isn't delayed by that slow sidebar.
  • Client-Side Navigation: It is particularly effective for Single Page Application (SPA) transitions where you want the "feel" of an instant app.

Key Limitation: SEO & Meta Tags

Do not use lazy fetching for data required for SEO. Because useLazyFetchdoes not block the render, if you are using the fetched data to populate useHead(like a page title or meta description), search engine crawlers might see the "Loading..." state instead of your actual content. Use standard useFetch for any data that must be present in the initial HTML.

The "Lazy" Macro

You can achieve the same result using the lazyoption in the standard composables:

  • useLazyFetch(url) is identical to useFetch(url, { lazy: true }).
  • useLazyAsyncData(key, fn) is identical to useAsyncData(key, fn, { lazy: true }).

In Nuxt 3, passing headers or authentication tokens during Server-Side Rendering (SSR) requires specific care. Unlike the browser, which automatically attaches cookies to requests, the Nuxt server acts as a "proxy" and does not share the user's browser state unless you manually forward it.


1. Forwarding Client Headers (Browser ? Nuxt Server ? API)

If your API relies on cookies or headers (like Authorization) that the browser originally sent to Nuxt, you must "proxy" them using the useRequestHeaders composable.

Header Type Implementation Why?
All Headers headers: useRequestHeaders() Passes everything (cookies, auth, user-agent) to the API.
Cookies Only headers: useRequestHeaders(['cookie']) Prevents unnecessary header bloat; only sends session data.
Specific Header headers: useRequestHeaders(['authorization']) Ideal for Bearer tokens sent from the client.
Example

2. Attaching Authentication Tokens (Manual)

When using JWT tokens stored in a cookie or state, you should attach them using the Authorization header.


The useRequestFetch Pattern

For more advanced scenarios, Nuxt provides useRequestFetch. This utility creates a version of $fetch This utility creates a version of

  • When to use: When you are calling an internal Nuxt API route (/api/...) from within useAsyncData.
  • Advantage: It handles the "context forwarding" automatically so you don't have to manually map headers.

Security Warning: Avoid Header Leakage

Be extremely careful when using useRequestHeaders() with external APIs. If you forward all client headers to a third-party service, you might accidentally leak sensitive user cookies or internal server headers to an untrusted source.

Tip:Always whitelist only the specific headers your API needs (e.g., 'cookie', 'authorization').


Summary Table
Header Type Implementation Why?
All Headers headers: useRequestHeaders() Passes everything (cookies, auth, user-agent) to the API.
Cookies Only headers: useRequestHeaders(['cookie']) Prevents unnecessary header bloat; only sends session data.
Specific Header headers: useRequestHeaders(['authorization']) Ideal for Bearer tokens sent from the client.

Nuxt 3 leverages its powerful server engine, Nitro, to provide a multi-layered caching system. This allows you to cache everything from entire HTML pages down to individual server functions or raw API responses.

The caching strategy you choose depends on whether you want to cache the entire route, an API endpoint, or a specific expensive function.


1. Route-Level Caching (Hybrid Rendering)

The simplest way to cache is through routeRules in nuxt.config.ts. This tells the server how to treat specific URL patterns without writing any custom server code.

Strategy Behavior Best Use Case
swr Serves stale cache while revalidating in the background. Product listings, blog feeds.
isr Similar to SWR but often used with specific TTL (Time To Live). Dynamic content that updates hourly.
prerender Generates a static HTML file at build time. Landing pages, "About Us" pages.

2. API & Event Handler Caching

If you have custom API routes in server/api/, you can use Nitro's specialized handlers to cache their JSON responses.

  • defineCachedEventHandler: Wraps an entire API route. The first user triggers the logic; subsequent users get the cached JSON result.
  • defineCachedFunction: Caches the result of a specific utility function used within multiple handlers.

3. Low-Level Storage (useStorage)

For manual control, Nuxt provides a unified Storage Layer called unstorage. You can get, set, and expire data manually using different "drivers" (Memory, Redis, Filesystem).


Summary of Caching Layers
Layer Tool Controlled By
Browser/CDN Cache-Control headers routeRules.headers
Page/HTML SWR / ISR routeRules
API Response defineCachedEventHandler Nitro Cache
Logic/Data defineCachedFunction Nitro Cache
Raw Data useStorage Manual Implementation

Nuxt Modules are the building blocks of the Nuxt ecosystem. They are essentially functions that run sequentially when Nuxt starts in development mode or during the build process. Their primary purpose is to automate the configuration and extension of your application.

Instead of manually configuring 10 different files to add a feature like Tailwind CSS or a Sitemap, a module handles the setup for you in a single line.


How Modules Extend Nuxt

Modules have deep access to the Nuxt "hooks," allowing them to modify almost any part of the framework's behavior.

Feature How a Module Modifies It
Auto-imports Adds new composables (e.g., useSupabaseUser) to your project.
Components Automatically registers UI components (e.g., <Icon /> or <NuxtImg />).
Nitro Server Injects new server routes, storage drivers, or middleware.
Build Process Modifies Vite or Webpack configurations to handle files like .svg or .sass.
Templates Injects code into app.vue or the HTML head (e.g., Google Analytics scripts).

The Three Types of Modules

  • Official Modules: Maintained by the Nuxt team (e.g., @nuxt/image, @nuxt/content, @nuxt/ui).
  • Community Modules:: Maintained by the open-source community (e.g.,lucide-nuxt, nuxt-simple-sitemap).
  • Local Modules:: Custom logic written inside your own project (usually in a modules/ folder) to keep your nuxt.config.ts.

Using a Module

To use a module, you typically follow a two-step process:

  • Install the package:: npm install @nuxtjs/tailwindcss
  • Register it:: Add it to the modules array in nuxt.config.ts.

Why Not Just Use Plugins?

While they sound similar, they serve different stages of the lifecycle:

  • Plugins: Run at runtime (when the app starts in the browser or on the server). Use them for things like global directives or injecting variables into the Vue instance.
  • Modules: Run at build-time. Use them to change the actual structure of the app, add files, or configure the build engine.

The Module Builder

If you want to create your own module to share with others, Nuxt provides a starter kit called nuxt-module-builder. It streamlines the process of writing, testing, and publishing a module to the official Nuxt Modules directory.

Creating a custom Nuxt module allows you to encapsulate reusable logic, register components, or hook into the Nuxt lifecycle. You can create a local module for a single project or use the Starter Kit to build a package for NPM.

1. Creating a Simple Local Module

For internal project logic, you don't need a complex setup. You can define a module directly in your modules/ directory.

File: modules/hello-world.ts


2. Using the Official Starter Kit (For Distribution)

If you intend to share your module via NPM, use the official template. It includes a src/ directory for logic and a playground/ directory for live testing.

Command:

npx nuxi init -t module my-awesome-module

Standard Module Structure:

| File/Folder | Purpose | | :--- | :--- |
  • | src/module.ts | The main entry point where you define hooks and configuration. |
  • | src/runtime/ | Contains code that will be injected into the user's app (plugins, components). |
  • | playground/ | A mini Nuxt app to test your module in real-time. |
  • | package.json | Manages dependencies and metadata (name, version). |

3. Common Module Tasks

Modules extend the framework by using Nuxt Kit>> utilities.

  • Adding a Component: addComponent({ name: 'MyButton', filePath: resolver.resolve('./runtime/MyButton.vue') })
  • Injecting a Composable: addImports({ name: 'useMyTool', from: resolver.resolve('./runtime/composables') })
  • Hooking into Nuxt:

4. Key Differences: Local vs. Published

  • Local: Registered innuxt.config.ts via relative path (e.g., '~/modules/my-module'). Perfect for site-specific optimizations.
  • Published:Installed via npm install and registered by package name. Requires proper bundling usingnpm run prepack.

While both Modules and Plugins are used to extend Nuxt, they operate at completely different stages of the application lifecycle. The simplest way to remember the difference is: Modules happen at "Build-time" (when you run npm run dev or build), while Plugins happen at "Runtime" (when the user actually opens the page).


Core Comparison Table

Feature How a Module Modifies It
Auto-imports Adds new composables (e.g., useSupabaseUser) to your project.
Components Automatically registers UI components (e.g., <Icon /> or <NuxtImg />).
Nitro Server Injects new server routes, storage drivers, or middleware.
Build Process Modifies Vite or Webpack configurations to handle files like .svg or .sass.
Templates Injects code into app.vue or the HTML head (e.g., Google Analytics scripts).

When to Use a Module

Use a Module if you need to change how Nuxt works or if you want to bundle multiple features together.

  • Automating Setup: Add tools like Tailwind CSS and configure them automatically without requiring manual setup from the user doing it manually.
  • Adding Components/Composables: Provide a reusable library of UI components or composables that are auto-imported into the app.
  • Modifying the Build: You need to change how Vite processes specific file types (like SVG or YAML).
  • Nitro Configuration: Inject server-side middleware, storage drivers, or modify Nitro configuration globally.

When to Use a Plugin

Use a Plugin if you need to provide something to your Vue components or handle logic during page load.

  • External Libraries: Initialize libraries that require access to the DOM or Vue instance (e.g., tooltip libraries or Google Analytics).
  • Global Helpers: Create reusable helpers like $formatDate that are available in every component via useNuxtApp().
  • Injected Keys: Providing a specific value (like a global configuration object) that components can inject.
  • Route Middleware: Attaching logic that runs specifically when the router initializes

The "Parent-Child" Relationship

The "Parent-Child" Relationship Module to install a Plugin.

  • Example: The @nuxtjs/supabase Module configures the project and then injects a Plugin so that you can use const supabase = useSupabaseClient() inside your components.

To register a third-party library globally in Nuxt 3, you create a file in the plugins/ directory. Nuxt automatically scans this folder and initializes these files during the application's startup.

Depending on the library, you might need to register it as a Vue Plugin (using .use()) or provide it as a helper accessible throughout your app via useNuxtApp().


Common Registration Patterns

Method Best For Implementation Goal
nuxtApp.vueApp.use() Vue UI Libraries Standard Vue components/directives (e.g., Vuetify, Floating Vue).
provide Logic-based SDKs Making a tool available globally as $myLibrary (e.g., Axios instance).
.client / .server Environment-specific Preventing a library that uses window from crashing the server.

Implementation Examples

1. Registering a Vue Component Library

If you are using a library like Floating Vue for tooltips, you register it directly to the Vue instance.

File: plugins/floating-vue.ts

2. Providing a Global Helper (Dependency Injection)

If you want to make a library like LDAP or a custom Analytics instance available everywhere.

File: plugins/analytics.ts


Environment-Specific Plugins

Many third-party libraries (like Chart.js or Google Maps) require access to the window or document objects. If these run on the server, the app will crash. You can control this using file suffixes:

  • my-plugin.client.ts: Runs only on the browser side.
  • my-plugin.server.ts: Runs only on the server side.

Best Practices

Follow these best practices when working with plugins in Nuxt:

  • Avoid Overuse: Every plugin adds to the "Initial Bundle Size." Only register libraries globally if they are used on almost every page.
  • Tree Shaking: If a library supports it, prefer importing it locally within the component where it is needed instead of a global plugin.
  • Order Matters: If one plugin depends on another, prefix filenames with numbers (e.g., 01.auth.ts, 02.api.ts) to control execution order.

The server/ directory is what makes Nuxt 3 a full-stack framework. It allows you to write backend logic—like API endpoints, database connections, and server-side security—directly within your Nuxt project.

Everything in this folder is powered by Nitro, Nuxt's high-performance server engine, and it runs in a Node.js (or Edge) environment, completely separate from your Vue frontend code.


Core Structure of the server/ Directory
Sub-directory Purpose Access URL
api/ Standard JSON API endpoints. /api/your-file
routes/ Custom server routes without the /api prefix. /your-file
middleware/ Code that runs on every server request. N/A (Internal)
plugins/ Extends Nitro's runtime (e.g., connecting to a DB). N/A (Internal)
utils/ Shared helper functions for server-side code. N/A (Internal)

1. The api/ Directory (File-Based API)

Any file you place here automatically becomes an API endpoint. You use defineEventHandler to process requests.

  • Result: Sending a GET request to /api/hello returns the JSON object above.
  • Method Support: You can specify methods using suffixes like hello.get.ts or user.post.ts.

2. Server Middleware

Unlike frontend route middleware, server middleware runs for every single request made to the server (including requests for images or static assets). It's the perfect place for:

  • Logging requests.
  • Checking authentication headers.
  • Adding custom context to the event object.

3. Why Use the server/ Folder?

  • Security: Keep sensitive data like API keys or database credentials hidden from the browser.
  • Full-Stack Power: Handle tasks such as sending emails or processing Stripe payments without needing a separate backend like Express or Laravel.
  • Type Safety: Nuxt auto-generates TypeScript types for API routes, so useFetch('/api/hello') knows exactly what data is returned.
  • Performance: Nitro is highly optimized and can be deployed to serverless platforms like Vercel, Netlify, or Edge workers such as Cloudflare.

The "Server Utils" Advantage

Files in server/utils/ are auto-imported within your server handlers. If you create a database connection utility there, you can use it in any file inside server/api/ without a single import statement.

In Nuxt 3, API routes are created by placing files inside the server/api/ directory. These routes are powered by Nitro and use a file-based routing system similar to your Vue pages.

The Basic API Structure

Every API route must export a defineEventHandler function. This function receives an event object, which contains the request, response, and various utilities.

  • Endpoint: /api/hello
  • Response: { "message": "Hello from Nitro!" }

Handling Different HTTP Methods

You can handle specific methods (GET, POST, DELETE, etc.) by either checking the method inside the handler or using the filename suffix.

Filename Strategy Method Use Case
submit.post.ts POST Specific file for form submissions.
data.get.ts GET Specific file for fetching data.
user.ts Any Catch-all for all methods on that route.

Reading Request Data

Nitro provides several built-in utilities to extract data from the incoming request

1. Query Parameters

For URLs like /api/search?q=nuxt, use getQuery.

2. Request Body (POST/PUT)

To read JSON data sent in a request body, use readBody.

3. Route Parameters (Dynamic API)

For dynamic paths like /api/users/123, name your file server/api/users/[id].ts and use event.context.params.


Advanced API Features

  • Error Handling: Use createError to return specific HTTP status codes.
  • Runtime Config: Access your private environment variables using useRuntimeConfig(event).
  • Cookies: Use getCookie(event, 'name') and setCookie(event, 'name', value) to manage sessions.
Summary of Nitro Utilities
Utility Purpose
getQuery(event) Parses URL search parameters.
readBody(event) Parses the JSON body of a POST/PUT request.
getHeader(event, name) Retrieves a specific request header.
setResponseStatus(event, code) Manually sets the HTTP status code (e.g., 201).

In Nuxt, Middleware is code that runs before a specific event, typically a page navigation or a server request. Although they share a name, Route Middleware and Server Middleware are entirely separate systems that run in different environments.


Comparison: Route vs. Server Middleware

Feature Route Middleware Server Middleware
Environment Hybrid (Server & Client) Server-Only (Nitro)
Trigger Triggered by page navigation. Triggered by every server request.
Primary Use Auth guards, role checks, dynamic redirects. Logging, custom headers, body parsing.
Location middleware/ server/middleware/
Logic Type Vue/Nuxt-based (uses useAuth, etc.). Nitro/H3-based (uses event object).

1. Route Middleware (The "Frontend" Guard)

Route middleware runs within the Vue part of your application. It is ideal for logic that must happen before a user sees a page.

  • Global: Runs on every route change (e.g.,middleware/auth.global.ts).
  • Named: Defined in the middleware/ folder and manually applied to specific pages using definePageMeta.
Example (Named Auth Guard):

2. Server Middleware (The "Backend" Interceptor)

Server middleware runs on the Nitro server. It intercepts every single request to your server, including API calls (/api/...), static assets, and page requests.

Warning

Server middleware is global and cannot be restricted to specific pages. It should be used sparingly for "universal" tasks like adding security headers or logging telemetry.

Example (Request Logger):

When to Use Which?

  • Use Route Middleware when you want to protect a specific page (e.g., /dashboard) or check if a user has permission to view a UI component.
  • Use Server Middleware when you need to modify the response of an API endpoint or handle low-level server logic that doesn't care about Vue components.

To implement a protected route in Nuxt 3, you create a Named Route Middleware. This middleware checks for a user's session or authentication token and redirects them if they aren't authorized.

The 3-Step Implementation

1. Create the Middleware

Create a file in your middleware/ directory. Nuxt will automatically register this as a named middleware.

File: middleware/auth.ts

2. Apply to Specific Pages

File: pages/dashboard.vue

3. (Optional) Create a Global Guard

If you want to protect every page by default (except for login), rename the file with a .global suffix.

File: middleware/auth.global.ts


Best Practices for Route Guards

Feature Route Middleware Server Middleware
Environment Hybrid (Server & Client) Server-Only (Nitro)
Trigger Triggered by page navigation. Triggered by every server request.
Primary Use Auth guards, role checks, dynamic redirects. Logging, custom headers, body parsing.
Location middleware/ server/middleware/
Logic Type Vue/Nuxt-based (uses useAuth, etc.). Nitro/H3-based (uses event object).

Comparison: Where to apply Auth?

Nuxt Layers are a powerful architectural feature that allows you to extend a "base" Nuxt application into other projects. Think of a layer as a fully functional Nuxt project that can be merged into another project, sharing everything from components and composables to server routes and configuration.

This is the foundation for creating multi-tenant applications, white-label solutions, or complex enterprise monorepos.


How Layers Work

When you extend a layer, Nuxt performs a "deep merge" of the two projects. If both the layer and the child project have a components/ folder, Nuxt combines them. If there is a conflict (e.g., both have a Header .vue), the child project overrides the layer.

Feature Behavior in Layers
Components & Composables Automatically merged and auto-imported.
Pages & Middleware Merged; child pages take priority over layer pages.
Server Engine (Nitro) Server API routes and middleware are merged.
nuxt.config.ts Configurations are deep-merged (arrays are concatenated, objects merged).
Public Assets Files in public/ are merged.

Implementation: Extending a Layer

To use a layer, you simply point to its directory (local) or its theme name (remote) in your nuxt.config.ts.


Key Use Cases

1. Enterprise Monorepos

If you have five different internal tools, you can create a core-layer containing the company’s design system, authentication logic, and API utilities. Each tool then extends this core layer, ensuring consistency and reducing code duplication.

2. White-Labeling

You can build a "Base Product" as a layer and then create multiple "Brand" projects that extend it. Each brand project only needs to override specific CSS, logos, or localized text while inheriting all the core functionality.

3. Reusable Starters (Themes)

Instead of cloning a "Starter Template" (which becomes stale), you can extend a starter layer. When the starter layer is updated, your project receives the updates via a simple package pull.


Layers vs. Modules

While they seem similar, their intent is different:

  • Modules Best for integrating tools or adding specific technical features (e.g., Image optimization, Sitemaps).
  • Layers: Best for sharing application logic and structure (e.g., a whole "Blog" system or "Auth" system).

In Nuxt 3, environment variables are managed through the Runtime Config. This system is superior to standard process.env because it allows for a clear separation between variables that are safe for the browser and those that must remain hidden on the server.


1. Defining Variables in nuxt.config.ts

You define your variables within the runtimeConfig block. Nuxt distinguishes between private and public keys based on how they are nested.

Configuration Key Accessibility Best For
runtimeConfig (Top-level) Server-only Secret API keys, DB passwords, private tokens.
runtimeConfig.public Client & Server Public API URLs, Firebase keys, Analytics IDs.
Example Setup:

2. Using.evuFiles

In your project root, create a .env file. Nuxt automatically maps these to your runtimeConfig based on a naming convention.

  • Prefix: UseNUXT_
  • Case: Uppercase
  • Nesting: Use double underscores __ for nested public variables.

3. Accessing Variables in the App

Use the useRuntimeConfig() composable to access your values.

In a Component or Composable: In a Server Route (server/api):

The server has access to both public and private keys.


4. Why is this secure?

  • Serialization Control: Nuxt only serializes thepublic object into the payload sent to the browser. The top-level runtimeConfig never leaves the server environment.
  • Runtime Overrides: You can change these variables in production (e.g., on Vercel or Docker) without rebuilding your app, simply by updating the environment variables on the host.

Security Checklist

  • Do not use process.env directly in your Vue components; it is not reactive and can lead to unexpected behavior in SSR.
  • Add .evu to .gitignore to prevent secrets from being pushed to version control.Validate critical variables in a server/plugins file to ensure the app doesn't start if a database URL is missing.

Nitro is the next-generation server engine built specifically for Nuxt 3. While Nuxt 2 relied on a heavy, Node-only server, Nitro is designed to be platform-agnostic, allowing Nuxt applications to run virtually anywhere—from traditional servers to "The Edge" (like Cloudflare Workers).


Core Characteristics of Nitro

Nitro is significant because it transforms Nuxt from a simple frontend framework into a high-performance, full-stack deployment powerhouse.

Feature Description Benefit
Cross-Platform Generates specific output for Vercel, Netlify, Docker, AWS, etc. Deploy once, run anywhere without code changes.
Serverless Optimized Minimal cold-start times and tiny bundle sizes. Better performance on AWS Lambda and Vercel.
Hybrid Rendering Supports SSR, CSR, SWR, and ISR simultaneously. Mix static and dynamic content on a per-route basis.
Storage Layer A unified API (unstorage) for Redis, FS, or Memory. Switch databases or cache drivers with a config change.

Why Nitro is a Game-Changer

1. The "Edge" Revolution

Before Nitro, running a Vue app with SSR on the Edge (geographically close to the user) was extremely difficult due to Node.js dependencies. Nitro uses a portable runtime that doesn't rely on Node.js-specific APIs, enabling your site to load in milliseconds globally.

2. Cold Start Performance

Nitro uses Rollup to bundle your server code. By removing unused code and dependencies (tree-shaking), it produces a server entry point that is significantly smaller than traditional setups, resulting in near-instant "cold starts" in serverless environments.

3. API Engine

Nitro provides the engine for the server/ directory. It offers:

  • Auto-imports: No need to import defineEventHandler or readBody.
  • Zero-config: API routes are automatically mapped and typed.
  • Direct calling: When the client fetches a Nitro API route during SSR, Nitro calls the function directly instead of making an actual HTTP request, saving network overhead.

Key Nitro Utilities

When working in the server/ folder, you are interacting directly with Nitro/H3 utilities:

  • useStorage(): For persistent or temporary data storage.
  • useRuntimeConfig(): Accessing environment variables on the server.
  • defineCachedEventHandler(): For built-in server-side caching.

Nitro Deployment Presets

You don't have to configure your server for different hosts. Nitro detects your environment or lets you specify it in nuxt.config.ts:

Implementing Lazy Loading for heavy components is one of the most effective ways to reduce your initial JavaScript bundle size and improve your site's "Time to Interactive" (TTI). In Nuxt 3, this is primarily achieved using the Lazy prefix.


The Three Methods of Lazy Loading
Method Implementation Best Use Case
Lazy Prefix <LazyMyHeavyChart /> Modals, footers, or complex widgets toggled by user action.
Dynamic Import defineAsyncComponent() When you need advanced control (loading/error states).
Lazy Hydration hydrate-on-visible Content that is on the page but doesn't need to be interactive immediately.

1. The Lazy Prefix (Nuxt-Specific)

Nuxt automatically creates a separate JavaScript chunk for any component when you add the Lazy prefix to its name in the template. The code is only downloaded when the component is rendered.


2. Advanced Control with defineAsyncComponent

If you want to show a custom Loading Spinner while the heavy component is being downloaded, or handle a Timeout, use the Vue standard method.


3. Lazy Hydration (Nuxt 3.16+)

This is a newer feature that allows a component to be rendered on the server (good for SEO) but delays its interactivity on the client. This prevents "heavy" JS from blocking the main thread during initial load.


Best Practices & Pitfalls

  • Always use v-if: If you use v-show with a Lazy component, the code will be downloaded immediately because v-show keeps the element in the DOM (just hidden).
  • Don't Lazy Load Above-the-Fold: Never lazy load your main hero section or header. This can cause layout shifts and hurt your Largest Contentful Paint (LCP) score.
  • Use prefetchComponents: If you know a user is likely to click a button (e.g., hovering over it), you can manually start the download:

Nuxt Islands (also known as Server Components) are a specialized way to render components only on the server. Unlike standard Vue components that send JavaScript to the browser to become interactive ("hydration"), Islands send pure HTML and CSS to the client.

This architecture allows you to create "islands" of static content within an otherwise dynamic application, drastically reducing the amount of JavaScript the user has to download.


How Islands Differ from Standard Components
Feature Standard Component Nuxt Island (Server Component)
Rendering Server + Client (Hydration) Server-only (No Hydration)
JavaScript Sent Full component logic + dependencies. Zero JavaScript.
Interactivity Fully reactive (clicks, state). Non-interactive (Static HTML).
Data Fetching Can happen on both sides. Only happens on the server.
Best For Forms, Dashboards, Interactivity. Blogs, Marketing sections, Static Lists.

1. How to Use Islands

To create an island, you simply suffix your component filename with .server.vue.

File components/StaticReport.server.vue

Usage in a page:

2. The NuxtIsland Component

You can also load islands dynamically using the component. This is useful for loading remote components or content that shouldn't block the rest of the page.


3. Why use Islands?

  • Massive Performance Gains: If you have a component that uses a heavy library (like a Markdown parser or a Syntax Highlighter), using an Island means the library never reaches the client's browser.
  • SEO Friendly: Since the output is pure HTML, search engines can index the content perfectly without needing to execute JavaScript.
  • Hybrid Interactivity: You can nest standard interactive components inside an Island using "Slots." The Island remains static, but the "Slot" content can be interactive.

Key Constraints

  • No Client Logic: You cannot use onMounted, watch, or event listeners like @click inside a .server.vue component.
  • Experimental: As of recent versions, this feature is still considered experimental. You must enable it in your nuxt.config.ts:

In Nuxt 3, handling errors is divided into two parts: catching application-level crashes (500 errors) and handling missing routes (404 errors).

1. The Custom Error Page (error.vue)

To create a global error interface, place a file named error.vue in the root directory of your project (alongside app.vue). This page acts as a "fallback" whenever Nuxt encounters an unhandled exception or a manual error.

File: /error.vue


2. Triggering Errors Manually

You can force the application to show the error page by using the createError or showError utilities.

Utility Behavior Best Use Case
createError Returns an error object. Used inside useFetch or useAsyncData to stop execution.
showError Immediately triggers the full-screen error page. Used in client-side logic (e.g., after a failed form submission).
clearError Clears the error state and redirects. Used on the error page "Close" or "Home" button.
Example (Inside a Page):

Note

Setting fatal: true ensures the error is caught during client-side navigation.


3. Handling 404s Specifically

While error.vue handles all errors, you may want a "Page Not Found" layout that stays within your site's standard design. You can achieve this using a Catch-all Route.

File: pages/[...slug].vue

4. Error Boundaries (NuxtErrorBoundary)

If you don't want the entire page to crash when a small component fails, use an Error Boundary. This allows you to catch errors locally and show a fallback UI while keeping the rest of the page interactive.


Summary of Strategies
Scenario Recommendation
Generic Crash Create error.vue in the root.
Route missing Create pages/[...slug].vue.
API data missing Use throw createError({ statusCode: 404 }).
Component failure Wrap in <NuxtErrorBoundary>.

Deploying a Nuxt 3 application is highly streamlined thanks to the Nitro engine, which automatically detects your deployment environment and configures the output accordingly.


1. Automated Deployment (Recommended)

Both Vercel and Netlify offer "Zero Config" deployments for Nuxt. The process is nearly identical for both platforms:

  • Push your code to a Git provider (GitHub, GitLab, or Bitbucket).
  • Import the project in the Vercel or Netlify dashboard.
  • Automatic Detection: The platforms will detect Nuxt 3 and set the following defaults:
    • Build Command: npm run build or npx nuxi build
    • Output Directory: .output/public (for static) or the platform's native function directory (for SSR).
  • Configure Environment Variables: Add any NUXT_PUBLIC_* or secret keys in the platform's "Environment Variables" settings.

2. Deployment Methods: SSR vs. Static

Depending on your nuxt.config.ts, Nitro will package your app differently.

Method Build Command Output Best For
SSR / Hybrid npm run build Serverless Functions Dynamic apps, personalized content, and APIs.
SSG (Static) npm run generate Static Files Blogs, documentation, and SEO-heavy sites.

3. Platform-Specific Notes

Vercel

Vercel is the native home of many Nuxt developers. It handles ISR (Incremental Static Regeneration) exceptionally well.

  • Edge Functions: You can run your Nuxt app on the Edge by setting nitro: { preset: 'vercel-edge' } in your config.
  • Analytics: Supports Vercel Speed Insights and Analytics out of the box.
Netlify

Netlify is excellent for high-traffic static sites and offers robust "Edge Rules."

  • Netlify Functions: Your server logic is automatically converted into Netlify Functions.
  • You can manage _redirects and _headers directly via routeRules in Nuxt.

4. Manual Preset Selection

If the auto-detection fails or you want to force a specific behavior, you can define the preset in your nuxt.config.ts:


5. Deployment Checklist

  • [ ] Runtime Config: Ensure all .env variables are added to the provider's dashboard.
  • [ ] Node Version: Ensure the provider is using Node.js 18.x or later (Nuxt 3 requirement).
  • [ ] Build Health: Run npm run build locally once to ensure there are no Nitro bundling errors before pushing.
  • [ ] Trailing Slashes: Check your routeRules if you have specific SEO requirements for URL structures.

In Nuxt 3, these three commands represent the primary stages of the application lifecycle: Development, Production (Dynamic), and Production (Dynamic).

Choosing the right command depends on whether you are currently coding or ready to deploy your site to the web.


Core Comparison Table
Command Environment Output Primary Goal
npm run dev Local Development Hot-reloaded code in memory. Instant feedback while coding.
npm run build Production (SSR) Executable Nitro server (.output). Dynamic apps requiring a server.
npm run generate Production (SSG) Static HTML/JS files (.output/public). Static hosting (S3, GitHub Pages).

1. npm run dev (Development)

This starts a local development server (usually at localhost:3000). It is optimized for speed and developer experience.

  • Hot Module Replacement (HMR): Changes in your components are reflected instantly without a full page reload.
  • Error Reporting: Provides detailed stack traces and the Nuxt DevTools overlay.
  • Server: Uses a local Nitro development server to handle API routes and SSR on the fly.

2. npm run build (Production SSR)

This command prepares your application for a Universal (SSR) deployment. It compiles your Vue code and your Nitro server logic into a production-ready bundle.

  • Output: Generates a .output folder containing a standalone Node.js server.
  • Behavior: The server will dynamically render pages and process API requests every time a user visits.
  • Best For: Applications with frequently changing data, user authentication, or complex API logic.

3. npm run generate (Production SSG)

This command triggers Static Site Generation. Nuxt crawls every route in your application and saves the result as a static HTML file.

  • Output: Generates static files in .output/public. No Node.js server is required for hosting.
  • Behavior: All data fetching is done at build time. When a user visits, they receive pre-rendered HTML.
  • Best For: Blogs, documentation sites, and marketing pages where speed and SEO are the highest priorities.

Which one should you use?

  • While coding: Use dev.
  • Deploying to Vercel/Netlify (Dynamic): Use build. The platform will automatically handle the serverless execution.
  • Deploying to a "Static Only" host: Use generate.

Pro Tip: In Nuxt 3, npm run generate is actually a shortcut for nuxt build --prerender. It still uses Nitro under the hood to "pre-render" your routes into static files.

Nuxt DevTools is one of the most powerful utilities in the Vue ecosystem, providing deep insights into your app's performance and structure directly within your browser.

To open it, ensure you are in development mode (npm run dev) and click the Nuxt icon at the bottom of your screen or press Shift + Alt + D.


Key Performance Debugging Features
Feature What it Monitors How to use it for Performance
Timeline Component renders and hydration. Identify "expensive" components that take too long to mount.
Payload Server-to-client data transfer. Check if you are sending too much unused data in useAsyncData.
Assets Image and font sizes. Find unoptimized images that are slowing down the page load.
Nitro Server-side tasks and API calls. Debug slow API responses or server-side logic bottlenecks.

1. Using the Timeline for Hydration

The Timeline tab is essential for identifying Hydration Mismatches and slow component mounting.

  • Look for: Long bars in the timeline. If a component takes 100ms+ to render, it might need to be optimized or lazy-loaded.
  • Action: If a component is visually "static" but takes a long time to hydrate, consider using Nuxt Islands or Lazy components.

2. Analyzing the Payload

When you fetch data on the server, Nuxt "serializes" it into a JSON object to pass it to the client.

  • Look for: Massive JSON objects in the Payload tab.
  • Action: Use the transform or pick options in useFetch to only return the keys you actually need. This reduces the HTML size significantly.

3. Inspecting Server Routes (Nitro)

Under the Server Routes tab, you can see every API endpoint available in your server/ directory.

  • Test Performance: You can trigger API calls directly from the DevTools to see their response time without refreshing the frontend.
  • Storage: Check the Storage section to see what is currently in your Nitro cache (Redis, memory, etc.).

4. Component Inspector

You can click the Inspector icon and hover over any element on your page.

  • It will show you exactly which Vue component rendered that element.
  • This is helpful for finding "hidden" heavy components that are nested deep within your layout.
How to Enable DevTools

If you don't see the icon, ensure it's enabled in your nuxt.config.ts:

Hydration is the process where a client-side JavaScript application "takes over" a static HTML page that was rendered by the server.

When you visit a Nuxt site, the server sends a fully rendered HTML document so the user sees content immediately. However, this HTML is "dead"—buttons don't work, and there is no reactive state. Hydration is the bridge that turns that static HTML into a live, interactive Vue application by connecting the browser's DOM to the Vue virtual DOM.


The Hydration Workflow
Step Action Environment
1. Render Nuxt converts Vue components into an HTML string. Server
2. Delivery The browser receives and displays the HTML + CSS. Browser
3. Download The browser downloads the JavaScript bundles. Browser
4. Hydrate Vue scans the HTML, builds the internal state, and attaches event listeners. Browser

Why Hydration Fails (Mismatches)

A Hydration Mismatch occurs when the HTML generated by the server does not exactly match the HTML the client expects to see. When this happens, Vue has to "bail out," discard parts of the server HTML, and re-render them, which causes a flicker and hurts performance.

class="h5-equivalent">Common Causes of Mismatches

  • Invalid HTML Structure: Browsers automatically "fix" bad HTML (e.g., putting a div tag inside a p tag). Since the server sends the raw "bad" HTML and the browser fixes it before Vue hydrates, the structures no longer match.
  • Environment-Specific Logic: Using checks like if (window) or if (process.client) inside your template.

  • The server doesn't know what window is, so it renders nothing. The client renders "Desktop." Mismatch!
  • >

    • Dates and Times: Rendering new Date() directly in a template. The server time and the client time will always be slightly different.
    • Randomness: Using Math.random() to generate IDs or values in a template.

    How to Fix Hydration Issues
    Strategy Tool/Component Purpose
    ClientOnly <ClientOnly> Wraps components that should only render on the client (e.g., a map).
    onMounted onMounted() hook Moves logic that depends on the browser to after hydration is complete.
    useId useId() A Nuxt 3.10+ composable that generates stable IDs for both server and client.
    Suppressing Warnings data-hydration-metadata (Internal) Helps Nuxt track specific nodes for debugging.

    Debugging Mismatches

    • Check the Console: Chrome will show a warning: "Hydration completed but contains mismatches." It usually points to the specific tag that failed.
    • Nuxt DevTools: The Timeline tab highlights hydration events and can help you spot which component triggered the warning.
    • View Source: Compare the "View Page Source" (Server HTML) with the "Inspect Element" (Client DOM) to find the difference.

    Integrating a Headless CMS with Nuxt 3 is typically handled through data-fetching composables (useFetch or useAsyncData) or dedicated modules. The goal is to fetch content from the CMS's API and map it to your Vue components.

    The Two Primary Integration Strategies

    Method Best For Implementation
    Official Modules Popular CMSs (Strapi, Contentful, Sanity) Use a pre-built module like @nuxtjs/strapi.
    Generic SDK / API Less common CMSs or Custom APIs Use the CMS’s JavaScript SDK or standard HTTP fetches.

    1. Using a Dedicated Module (e.g., Strapi)

    Dedicated modules handle authentication, base URLs, and TypeScript integration automatically.

    Setup:

    • Install: npm install @nuxtjs/strapi
    • Configure in nuxt.config.ts:
    Usage in a Page:

    2. Using a Generic SDK (e.g., Contentful)

    .

    If a module isn't available or you prefer the official SDK, you can initialize the client in a Nuxt Plugin or a Server Utility.

    Setup (Server-side recommended):

    Create a utility in server/utils/cms.ts to keep your API keys secure.


    3.optimizing for SEO and speed

    • Static Site Generation (SSG): Use npm run generate. Nuxt will fetch your CMS content during the build and save it as static HTML.
    • Incremental Static Regeneration (ISR): Use routeRules to re-fetch CMS data every X minutes without rebuilding the whole site.
    • 4.Handling Dynamic Previews

    • Image Optimization: Use the @nuxt/image module. Most Headless CMSs (like Contentful/Strapi) have built-in image providers that allow you to resize images on the fly via URL parameters.
    • Headless CMSs usually provide a "Preview" mode to see drafts.
    • Create a "Preview" route (e.g., /api/preview).
    • Use setCookie to enable a "preview mode" flag.
    • In your data fetching, check for the cookie to switch between the Production API and the Preview API.

    Moving from Nuxt 3 to Nuxt 4 is an "evolution, not a revolution." Unlike the massive rewrite required for Nuxt 2 to 3, Nuxt 4 focuses on refining performance, tightening type safety, and cleaning up the project structure.

    Here are the key breaking changes and architectural shifts to expect.


    The most visual change is the reorganization of your project files. By default, Nuxt 4 moves application-specific folders into a dedicated app/ directory to separate them from server logic and root-level config.

    Nuxt 3 Location Nuxt 4 Location Notes
    pages/ app/pages/ All frontend routing.
    components/ app/components/ All Vue components.
    app.vue app/app.vue The entry point moved into app/.
    server/ server/ Remains at the root (outside app/).
    public/ public/ Remains at the root.

    Pro Tip: This change is optional initially. Nuxt 4 will auto-detect your old structure, but migrating to the app/ folder improves file-watching performance on Windows and Linux.


    Data Fetching: Shallow Reactivity & Key Sharing

    Nuxt 4 streamlines useFetch and useAsyncData to be more performant and predictable.

    • by Default: Data returned from these composables is now a shallowRef. It only triggers updates if the entire object is replaced. If you need deep reactivity (e.g., updating a nested property and expecting the UI to react), you must explicitly set { deep: true }.
    • Keys: If two components call useFetch with the same key, they now share the same reactive state. If one component refreshes the data, the other updates automatically.
    • Null to Undefined: The default value for data and error is now undefined instead of null before the fetch completes.

    3. Stricter Type Boundaries

    Nuxt 4 introduces Project References for TypeScript. It now treats your project as three distinct TypeScript "sub-projects":

    • App: Your Vue/client code.
    • Server: Your Nitro/API code.
    • Shared: Code accessible by both.

    The Impact: You can no longer accidentally import server-only types into your client-side components (and vice versa), which prevents "type leak" bugs that were common in Nuxt 3.

    4. Component Naming Standardization

    In Nuxt 3, component names in Vue DevTools or when used as strings (like in <component :is="...">) could be inconsistent. Nuxt 4 standardizes this:

    • Names are now strictly generated based on their path and filename.
    • This ensures that what you see in the code matches exactly what you see in the DevTools and Vue’s internal registry.

    5. Removal of Legacy & Experimental Features

    • Several features that were "experimental" in Nuxt 3 have been removed or made the permanent default:
    • Removed: window.__NUXT__ (Global data is now handled via useNuxtApp()).
    • Removed: Support for .ejs template compilation.
    • Locked Features: Options like treeshakeClientOnly and configSchema are now permanently enabled and can no longer be toggled off.
    How to Prepare for Migration

    You can start moving toward Nuxt 4 today while still on Nuxt 3 by enabling the "future" flags in your config:

    From The Same Category

    NestJS

    Browse FAQ's

    Next.js

    Browse FAQ's

    Ruby On Rails

    Browse FAQ's

    Express.js

    Browse FAQ's

    Spring Boot

    Browse FAQ's

    Laravel

    Browse FAQ's

    Flask

    Browse FAQ's

    DocsAllOver

    Where knowledge is just a click away ! DocsAllOver is a one-stop-shop for all your software programming needs, from beginner tutorials to advanced documentation

    Get In Touch

    We'd love to hear from you! Get in touch and let's collaborate on something great

    Copyright copyright © Docsallover - Your One Shop Stop For Documentation