DEV Community

Tianya School
Tianya School

Posted on

Nuxt.js in action: Vue.js server-side rendering framework

Create a Nuxt.js project

First, make sure you have installed Node.js and yarn or npm. Then, create a new Nuxt.js project through the command line:

yarn create nuxt-app my-nuxt-project
cd my-nuxt-project
Enter fullscreen mode Exit fullscreen mode

During the creation process, you can choose whether you need options such as UI framework, preprocessor, etc., and configure them as needed.

Directory structure

Nuxt.js follows a specific directory structure, some of the key directories are as follows:

├── .nuxt/ # Automatically generated files, including compiled code and configuration
├── assets/ # Used to store uncompiled static resources, such as CSS, images, fonts
├── components/ # Custom Vue components
├── layouts/ # Application layout files, defining the general structure of the page
 └── default.vue # Default layout
├── middleware/ # Middleware files
├── pages/ # Application routes and views, each file corresponds to a route
 ├── index.vue # Default homepage
 └── [slug].vue # Dynamic routing example
├── plugins/ # Custom Vue.js plugins
├── static/ # Static resources, will be copied to the output directory as is
├── store/ # Vuex state management file
 ├── actions.js # Vuex actions
 ├── mutations.js # Vuex mutations
 ├── getters.js # Vuex getters
 └── index.js # Vuex store entry file
├── nuxt.config.js # Nuxt.js configuration file
├── package.json # Project dependencies and scripts
└── yarn.lock # Or npm.lock, record dependency versions
Enter fullscreen mode Exit fullscreen mode
  • .nuxt/: This directory is automatically generated and contains compiled code. Generally, it does not need to be modified directly.
  • assets/: Stores uncompiled static resources, such as CSS, JavaScript, and images. Nuxt.js will process these resources during the build.
  • components/: Stores custom Vue components that can be reused in different parts of the application.
  • layouts/: Defines the layout of the page. There can be a default layout or multiple specific layouts.
  • pages/: Each file corresponds to a route, and the file name is the route name. Dynamic routes are represented by square brackets [].
  • middleware/: Place custom middleware, which can execute logic before and after page rendering.
  • plugins/: Custom entry file for Vue.js plugins.
  • static/: Directly copied to the build output directory without any processing, often used to store robots.txt or favicon.ico, etc.
  • store/: Vuex state management directory, storing actions, mutations, getters and the entry file of the entire store.
  • nuxt.config.js: Nuxt.js configuration file, used to customize project settings.
  • package.json: Project dependencies and script configuration.
  • yarn.lock or npm.lock: Record the exact version of project dependencies to ensure dependency consistency in different environments.

Page rendering

Create an index.vue file in the pages/ directory. This is the homepage of the application:

<!-- pages/index.vue -->
<template>
  <div>
    <h1>Hello from Nuxt.js SSR</h1>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'This content is server-rendered!'
    };
  },
  asyncData() {
    // Here you can get data on the server side
    // The returned data will be used as the default value of data
    return { message: 'Data fetched on server' };
  }
};
</script>
Enter fullscreen mode Exit fullscreen mode

The process of Nuxt.js page rendering is divided into two main stages: server-side rendering (SSR) and client-side rendering (CSR). Here are the detailed steps of Nuxt.js page rendering:

Initialization:

The user enters the URL in the browser and sends a request to the server.

After the server receives the request, it starts processing.

Route resolution:

Nuxt.js uses the routes configuration in nuxt.config.js (if it exists) or automatically generates routes from the pages/ directory.

The corresponding page file is identified, such as pages/index.vue or pages/about.vue.

Data prefetching:

Nuxt.js looks for asyncData or fetch methods in the page component (if they exist).

These methods will run on the server side to get data from APIs or other data sources.

After the data is obtained, it will be serialized and injected into the page template.

Template rendering:

Nuxt.js uses Vue.js's rendering engine to convert components and pre-fetched data into an HTML string.
The HTML string contains all the initial data required by the client, inlined in the <script> tag in JSON format.

Return HTML:

The server sends the generated HTML response back to the client (browser).

Client initialization:

After the browser receives the HTML, it begins parsing and executing inline JavaScript.
The Nuxt.js client library (nuxt.js) is loaded and initialized.

Client rendering:

The client library takes over the rendering, the Vue.js instance is created, and the data is injected from the inline JSON into the Vue instance.
The page completes the initial rendering and the user can see the complete page content.
At this point, the page is interactive and the user can trigger events and navigate.

Subsequent navigation:

When the user navigates to other pages, Nuxt.js uses client-side routing (Vue Router) for non-refresh jumps.
If the new page requires data, the asyncData or fetch method will run on the client, fetch the new data and update the view.

SSG (Static Site Generation):

Outside of development, you can use the nuxt generate command to generate static HTML files.

Each page will be pre-rendered as a separate HTML file with all the necessary data and resources.

Using asyncData

The asyncData method is unique to Nuxt.js and allows you to pre-fetch data on the server and reuse it on the client. In the example above, we simply changed the value of message, but in a real application, you might call an API to fetch data here.

Middleware

Middleware (Middleware) is a feature that allows you to execute specific logic before and after route changes. Middleware can be used globally, at the page level, or at the layout level to handle tasks such as authentication, data preloading, route guards, etc.

1. Global middleware

Global middleware is configured in the nuxt.config.js file and affects all pages in the application:

// nuxt.config.js
export default {
// ...
    router: {
        middleware: ['globalMiddleware1', 'globalMiddleware2'] // can be a string array
    }
};
Enter fullscreen mode Exit fullscreen mode

Middleware files are usually located in the middleware/directory, such as middleware/globalMiddleware1.js:

// middleware/globalMiddleware1.js
export default function (context) {
    // context contains information such as req, res, redirect, app, route, store, etc.
    console.log('Global Middleware 1 executed');
}
Enter fullscreen mode Exit fullscreen mode

2. Page-level middleware

Page-level middleware only affects specific pages. Declare middleware in the page component:

// pages/about.vue
export default {
    middleware: 'pageMiddleware' // can be a string or a function
};
Enter fullscreen mode Exit fullscreen mode

The corresponding middleware file is located in the middleware/directory, for example, middleware/pageMiddleware.js:

// middleware/pageMiddleware.js
export default function (context) {
    console.log('Page Middleware executed');
}
Enter fullscreen mode Exit fullscreen mode

3. Layout-level middleware

Layout-level middleware is similar to page-level, but it applies to all pages that use the layout. Declare middleware in the layout component:

// layouts/default.vue
export default {
    middleware: ['layoutMiddleware1', 'layoutMiddleware2']
};
Enter fullscreen mode Exit fullscreen mode

The corresponding middleware file is located in the middleware/directory:

// middleware/layoutMiddleware1.js
export default function (context) {
    console.log('Layout Middleware 1 executed');
}

// middleware/layoutMiddleware2.js
export default function (context) {
    console.log('Layout Middleware 2 executed');
}
Enter fullscreen mode Exit fullscreen mode

Context of middleware

The middleware function receives a context object as a parameter, which contains the following properties:

  • req (HTTP request object, valid only on the server side)

  • res (HTTP response object, valid only on the server side)

  • redirect (function used for redirection)

  • app (Vue instance)

  • route (current route information)

  • store (Vuex Store, if enabled)

  • payload (if there is data returned by asyncData)

Middleware can be executed sequentially, and each middleware can decide whether to continue executing the next middleware in the chain or interrupt the route through the redirect function.

Dynamic Routing

Nuxt.js supports dynamic routing, which is very useful for handling content with dynamic IDs such as blog posts, user profiles, etc. Create a dynamic routing file in the pages/ directory, such as [id].vue:

<!-- pages/post/[id].vue -->
<template>
  <div>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </div>
</template>

<script>
export default {
  async asyncData({ params, $axios }) {
    const response = await $axios.$get(`/api/posts/${params.id}`);
    return { post: response.post };
  }
};
</script>
Enter fullscreen mode Exit fullscreen mode

Here [id] represents a dynamic parameter, asyncData will automatically process this parameter and get the blog post with the corresponding ID.

Layout
Layout allows you to define the common structure of global or specific pages. Create a default.vue file in the layouts/ directory:

<!-- layouts/default.vue -->
<template>
  <div>
    <header>
      <nav>
        <!-- Navigation links, etc. -->
      </nav>
    </header>
    <main>
      <nuxt /> <!-- The page content will be inserted here -->
    </main>
    <footer>
      <!-- Bottom information, etc. -->
    </footer>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

By default, all pages will use this layout. If you want to set a different layout for a specific page, you can specify it in the page component:

// pages/about.vue
export default {
  layout: 'custom' // Create custom.vue under layouts/
};
Enter fullscreen mode Exit fullscreen mode

Plugin and library integration
Nuxt.js supports Vue.js plugins, which you can configure in nuxt.config.js:

// nuxt.config.js
export default {
  plugins: [
    { src: '~plugins/vuetify.js', ssr: true },
    { src: '~plugins/vue-chartjs.js', mode: 'client' } // Run only on the client side
  ]
};
Enter fullscreen mode Exit fullscreen mode

Then create the corresponding files in the plugins/ directory, such as vuetify.js:

// plugins/vuetify.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';

Vue.use(Vuetify);
Enter fullscreen mode Exit fullscreen mode

Configuration and Optimization

Nuxt.js Configuration File (nuxt.config.js)

nuxt.config.js is the main configuration file for Nuxt applications, which is used to customize the behavior of the application. Here are some commonly used configuration items:

  • mode: Set the running mode of the application. The optional values ​​are 'spa' (single-page application), 'universal' (server-side rendering) and 'static' (static generation). The default is 'universal'.
  • head: Configure the part of the page, such as title, metadata, links, etc.
  • css: Specify the global CSS file, which can be an array of file paths.
  • build: Configure the build process, such as transpile, extractCSS, extend, etc. For example, you can add a Babel plugin or adjust the Webpack configuration here.
  • router: Customize routing configuration, such as base path, mode, etc.
  • axios: Configure the axios module, including base URL, proxy settings, etc.
  • plugins: Register global Vue plugins, which can be specified to be loaded on the client or server.
  • modules: Load external modules, such as @nuxtjs/axios, @nuxtjs/proxy, etc.
  • env: Define environment variables, which will be injected into the client and server at build time.
// nuxt.config.js
export default {
  // Project Name
  name: 'my-nuxt-app',

    // Project mode: spa, universal, static
    mode: 'universal', // Default value, supports server-side rendering

    // Application meta information
  head: {
    title: 'My Nuxt App',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'App description' }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },

  // CSS Styles
  css: [
    '@/assets/css/main.css'
  ],

  // Routing Configuration
  router: {
    base: '/my-nuxt-app/', // The base path of the application
    extendRoutes(routes, resolve) {
      // Manually extend or modify routes
    }
  },

    // Build Configuration
    build: {
        transpile: [/^my-vue-component/], // Modules that need to be translated
        vendor: ['lodash'], // Public library, pre-packaged
        extractCSS: true, // Extract CSS to a separate file
        optimization: {
        splitChunks: {
            cacheGroups: {
            vendors: {
                test: /[\\/]node_modules[\\/]/,
                priority: -10,
                filename: 'vendors.js'
            }
            }
        }
        }
    },
    // Axios configuration
    axios: {
        baseURL: process.env.BASE_URL || 'http://localhost:3000/api', // API base URL
        browserBaseURL: 'https://api.example.com' // Client API base URL
        },

    // Plugins
    plugins: [
        { src: '@/plugins/vue-my-plugin', ssr: false } // Asynchronously loaded plugin, ssr: false means only client loading
    ],

    // Modules
    modules: [
        '@nuxtjs/axios', // Install and configure axios module
        '@nuxtjs/pwa' // Install and configure PWA module
    ],

    // Environment variables
    env: {
        apiKey: 'your-api-key',
        apiUrl: 'https://api.example.com'
    },

    // Vuex Store configuration
    store: true, // Automatically create Vuex store
    loading: { color: '#3B8070' }, // Loading indicator color

    // Server-side middleware
    serverMiddleware: [
        { path: '/api', handler: '~/api/index.js' } // Use custom server-side middleware
    ],

    // Static generation configuration
    generate: {
        dir: 'dist', // Output directory
        fallback: true, // Generate 404 page for dynamic routes that are not pre-rendered
        routes: () => ['/about', '/contact'] // Pre-rendered specified routes
    }
};
Enter fullscreen mode Exit fullscreen mode

Optimization strategy

  • Asynchronous data prefetching (asyncData/fetch): Use asyncData or fetch methods to prefetch data on the server side to reduce the burden of client rendering.
  • Code splitting: Nuxt.js automatically splits the code to ensure that the relevant code is loaded only when the route is visited.
  • Static site generation (SSG): Use the nuxt generate command to generate static HTML files, which is suitable for sites with infrequent content changes, improving loading speed and SEO friendliness.
  • Cache strategy: Use HTTP cache strategies such as ETag and Last-Modified to reduce duplicate requests.
  • Vue.js optimization: Ensure the optimization of Vue components, such as avoiding useless watchers, using v-once to reduce re-rendering, etc.
  • Image optimization: Use the correct image format (such as WebP), ensure that the image size is appropriate, and use lazy loading technology.
  • Service Worker: Integrate PWA support and use Service Worker for offline caching and push notifications.
  • Tree Shaking: Make sure your dependencies support Tree Shaking to remove unused code.
  • Analysis and Monitoring: Use nuxt build --analyze or integrate third-party tools (such as Google Lighthouse) for performance analysis to continuously monitor application performance.

Static Site Generation (SSG)

Nuxt.js's static site generation (SSG) is implemented through the nuxt generate command. This command traverses the application's routes and generates a pre-rendered HTML file for each route, which can be directly deployed to any static file hosting service. Here are some key points about SSG:

1. Configuration: In the nuxt.config.js file, you can configure the generate option to control the behavior of static generation:

export default {
    generate: {
        dir: 'dist', // Output directory, default is dist
        fallback: true, // Generate 404 page for dynamic routes that are not pre-rendered
        routes: () => ['/about', '/contact'], // Predefined static routes
        exclude: ['/admin/*'], // Exclude certain routes
        interval: 5000, // Generation interval, in milliseconds
        concurrency: 10 // Number of concurrently generated routes
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Generate: Run npm run generate or yarn generate to start the static generation process. Nuxt.js will generate the corresponding HTML file according to the configuration in generate.routes. If not explicitly defined, it will automatically scan all files under the pages/ directory to generate routes.

3. Data prefetching: In the page component, you can use the asyncData or fetch method to prefetch data. This data will be injected into the HTML when the static page is generated, so that the page does not require additional requests when the client loads:

// pages/about.vue
export default {
    async asyncData({ params, $axios }) {
        const aboutInfo = await $axios.$get('/api/about')
        return { aboutInfo }
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Middleware processing: Server-side middleware will not be executed during the SSG process because SSG generates static files without a server environment. Therefore, if you need to execute some logic when generating, it is best to handle it in asyncData or fetch.

5. Deployment: The generated static files can be deployed to any static file hosting service, such as Netlify, Vercel, GitHub Pages, or AWS S3. These services usually do not require running any server-side code, just upload the generated dist folder.

6. SEO Optimization: SSG improves SEO because search engine crawlers can read pre-rendered HTML content without waiting for JavaScript to execute.

7. Dynamic Routes: For dynamic routes, Nuxt.js will try to generate all possible combinations. If all possible dynamic routes cannot be predicted, they can be manually specified in generate.routes, or controlled using generate.includePaths and generate.excludePaths.

8. 404 Page: Setting generate.fallback to true will generate a 404 page for dynamic routes that are not pre-rendered. When users visit these routes, Nuxt.js will try to render them on the client side.

Run the nuxt generate command and Nuxt.js will generate static HTML files.

Validation and Error Handling

Validation

Validation usually involves input validation of form data or API requests. Nuxt.js itself does not directly provide a validation library, but you can integrate third-party libraries such as Vuelidate, vee-validate, or use TypeScript for type checking.

Using Vee-Validate
1. Installation: First, you need to install the vee-validate library:

npm install vee-validate
Enter fullscreen mode Exit fullscreen mode

2. Configuration: Add the Vue plugin configuration in nuxt.config.js:

   export default {
     plugins: [{ src: '~/plugins/vee-validate', ssr: false }]
   };
Enter fullscreen mode Exit fullscreen mode

3. Create a plugin: Configure Vee-Validate in plugins/vee-validate.js:

   import Vue from 'vue';
   import VeeValidate from 'vee-validate';

   Vue.use(VeeValidate);
Enter fullscreen mode Exit fullscreen mode

4. Usage: Use Vee-Validate for form validation in your component:

   <template>
     <form @submit.prevent="submitForm">
       <input v-model="email" name="email" v-validate="'required|email'"/>
       <span>{{ errors.first('email') }}</span>
       <button type="submit">Submit</button>
     </form>
   </template>
   <script>
   export default {
     data() {
       return {
         email: ''
       };
     },
     methods: {
       submitForm() {
         this.$validator.validateAll().then(result => {
           if (result) {
             // Verify success logic
           } else {
             // Validation failure logic
           }
         });
       }
     }
   };
   </script>
Enter fullscreen mode Exit fullscreen mode

Error handling

Nuxt.js provides several ways to handle errors, including global error handling and page-specific error handling.

Global error handling

  • Custom error page: Create an error.vue file in the layouts directory to customize the error page layout.
  • Capture global errors: Configure the error property in nuxt.config.js to capture global errors:
export default {
    error: {
        // Handling when the page does not exist
        pageNotFound({ error, store, app, env }) {
        // Handling logic
        },
        // Handling any errors
        handler(error, { error: nuxtError, store, app, env }) {
        // Handling logic
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

Page-specific error handling

In the page component, you can use the try-catch structure of the asyncData or fetch method to handle errors:

export default {
  async asyncData({ params, error }) {
    try {
      const data = await fetchSomeData(params.id);
      return { data };
    } catch (err) {
      error({ statusCode: 500, message: 'Data acquisition failed' });
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

API request error handling

For API requests, if you use the @nuxtjs/axios module, you can handle errors uniformly in the request interceptor:

// plugins/axios.js
import axios from 'axios';
import { toast } from '~/utils/toast';

axios.interceptors.response.use(null, (error) => {
  const { status } = error.response;
  if (status === 401) {
    // Handling unauthorized errors
  } else if (status >= 500) {
    // Handling Server Errors
    toast.error('Server Error');
  }
  return Promise.reject(error);
});

export default ({ $axios }, inject) => {
  inject('axios', $axios);
};
Enter fullscreen mode Exit fullscreen mode

Make sure to register this plugin in nuxt.config.js.

Vue Ecosystem Integration

Vue Router:

Nuxt.js automatically generates a routing system for your application based on the file structure. Routing configuration usually does not need to be written manually, but can be extended through the router property of nuxt.config.js.

Vuex:

Nuxt.js automatically creates a Vuex store. Under the store directory, you can create modular state, mutations, actions, and getters. For example, create a store/modules/users.js file to manage user data.

   // store/modules/users.js
   export const state = () => ({
     users: []
   });

   export const mutations = {
     SET_USERS(state, payload) {
       state.users = payload;
     }
   };

   export const actions = {
     async fetchUsers({ commit }) {
       const response = await this.$axios.get('/api/users');
       commit('SET_USERS', response.data);
     }
   };
Enter fullscreen mode Exit fullscreen mode

Vue CLI:

Nuxt.js provides its own build tool, but it is also based on Vue CLI. This means you can use command-line tools similar to Vue CLI, such as npx nuxt generate (static generation) or npx nuxt build (build application).

Babel:

Nuxt.js is configured with Babel by default to support the latest JavaScript features. You usually don't need to configure Babel manually unless there is a special need.

TypeScript:

To use TypeScript, set typescript: true in nuxt.config.js and Nuxt.js will automatically configure TypeScript support.

ESLint:

For code quality checking, you can install ESLint in your project and configure .eslintrc.js. Nuxt.js provides @nuxt/eslint-module plugin to simplify integration.

   // nuxt.config.js
   module.exports = {
     buildModules: [
       '@nuxt/typescript-build',
       '@nuxtjs/eslint-module' // Add ESLint integration
     ],
     eslint: {
       fix: true, // Automatically fix errors
       ignoreDuringBuilds: true // Ignore errors during build
     }
   };
Enter fullscreen mode Exit fullscreen mode

VueUse:

VueUse is a Vue use case library that contains various practical functions. To integrate, first install @vueuse/core, then import and use the functions in your components.

   npm install @vueuse/core
Enter fullscreen mode Exit fullscreen mode
   // In the component
   import { useCounter } from '@vueuse/core';

   export default {
     setup() {
       const count = useCounter(0); // Using the Counter Function
       // ...
     }
   };
Enter fullscreen mode Exit fullscreen mode

Vue plugins:

You can register Vue plugins globally through the plugins configuration item in nuxt.config.js. For example, integrate Vue Toastify to display notifications:

   // nuxt.config.js
   export default {
     plugins: [{ src: '~plugins/toastify.js', ssr: false }]
   };
Enter fullscreen mode Exit fullscreen mode
   // plugins/toastify.js
   import Vue from 'vue';
   import Toastify from 'toastify-js';

   Vue.use(Toastify);
Enter fullscreen mode Exit fullscreen mode

Workflow using Nuxt.js

Nuxt.js provides a complete workflow for development, building, and deployment. Use the nuxt command to start the development server, nuxt build for production building, nuxt start to start the production server, and nuxt generate to generate static files.

Performance optimization

  1. Static generation (SSG): Use the nuxt generate command to generate pre-rendered HTML files, which can greatly improve the first screen loading speed and is SEO-friendly.

  2. Code splitting: Nuxt.js will perform code splitting by default, dividing the application into multiple small blocks, and only load the code required by the current page, reducing the initial loading volume.

  3. Lazy loading: For large applications, you can consider lazy loading components or modules and only load them when needed. You can use <nuxt-child :lazy="true"> or <component :is="..."> combined with async components to achieve this.

  4. Optimize resources:

  • Images: Use the correct format (such as WebP), compress images, use lazy loading (<img :src="..." :loading="lazy">), or use nuxt-image or nuxt-picture components.

  • CSS: Extract CSS to a separate file and reduce inline styles.

  • JS: Use Tree Shaking to remove unused code.

  1. Asynchronous data prefetching: Use asyncData or fetch methods to preload data to ensure that the data is ready before rendering.

  2. Server-side caching: Use the nuxt-ssr-cache module to cache the results of server-side rendering and reduce unnecessary API calls.

  3. HTTP caching: Set the correct cache headers (such as Cache-Control) and use the browser to cache static resources.

  4. Route guards: Use route guards such as beforeRouteEnter to avoid loading data when it is not needed.

  5. Reduce HTTP requests: Combine multiple CSS and JS files to reduce the number of HTTP requests.

  6. Optimize API performance: Optimize the backend interface, reduce response time, and use paging, filtering, and caching strategies.

  7. Leverage CDN: Host static resources on CDN to speed up loading for global users.

  8. Optimize Vuex state management: Avoid unnecessary calculated properties and listeners to reduce the overhead of state changes.

  9. Performance audit: Use Lighthouse, Chrome DevTools, or other performance audit tools to regularly check application performance and make improvements based on the report.

  10. Service Worker: If applicable, integrate PWA features and use Service Worker for offline caching and resource preloading.

  11. Module optimization: Choose high-performance third-party modules and make sure they are optimized for SSR.

Top comments (0)