Additional SSR performance with Nuxt fullstack server (Part 2)

Additional SSR performance with Nuxt fullstack server


In Part 1, I described how easy it is to organize an API Server in Nuxt . In Part 2, I want to tell you what additional benefits you can get from the Nuxt fullstack server .

Part 2: speeding up server rendering!


Let's think now how our server works from the example codesandbox.io/s/codesandbox-nuxt-3gzhl

  1. The client requests the main page 3gzhl.sse.codesandbox.io
  2. Nuxt starts rendering on the server the page /pages/index.vue
  3. Comes to

      async fetch() {
        this.users = await this.$api("users", "index");
      },
    
  4. Via axios, he makes an http request , at 3gzhl.sse.codesandbox.io/api/users/index i.e. on myself
  5. A connection is established, a new session is created on the server, and memory is allocated for processing the http request
  6. An incoming request is accepted via http protocol , url is parsed, parameters are processed
  7. Executes server middleware
  8. Nuxt Launches Our API Server
  9. JSON
  10. users.index(), JSON
  11. JSON http
  12. axios JSON
  13. API

Now imagine that we have 20 components on the page that request data via the API , so in one request for the page with the Nuxt server, 20 additional internal http connections will be established and steps 4-13 will be performed 20 times. Nuxt HTTP server can process more than 55 thousand requests per second, however, creating internal HTTP requests, we reduce the potential server resources by tens of times.

But when we render the page on the server, we have direct access to all the controllers in the / api folder /

Let's change the logic so that when rendering on the server, the controller code is called directly, and when called from the browser, the request was sent via http

  1. Rename the file /plugins/api-context.js to /plugins/api-context.client.js
  2. change the file name in the settings /nuxt.config.js

      plugins: ["~/plugins/api-context.client.js"]
    

    Now this. $ Api context is available only for client code
  3. create this. $ api context to directly call controllers on the

    /plugins/api-context.server.js server
    export default (context, inject) => {
      inject("api", async (controller, method, params) => {
        try {
          let api = require("../api/" + controller.replace(/^\/+|\/+$|\.+/g, ""));
          return await api[method](params);
        } catch (e) {
          console.error(e);
          throw e;
        }
      });
    };
    
  4. connect the server plugin

    /nuxt.config.js
      plugins: [
        "~/plugins/api-context.client.js",
        "~/plugins/api-context.server.js"
      ]
    

Now the function this. $ Api on the server will directly call the controller method, and on the client this. $ Api send an http request through axios .

The code

  async fetch() {
    this.users = await this.$api("users", "index");
  },

when rendering on the server, it will not perform an http request to itself, but simply connect the /api/users.js file via require and call the index () method , i.e. items from 4-13 will not be executed, but only 10 will be executed. However, when the client clicks the Refresh button in the browser , the same data will be requested via http . Here is the full code: codesandbox.io/s/codesandbox-nuxt-pbriw





Performance testing


codesandbox.io/s/codesandbox-nuxt-rzdyw

  1. To eliminate the influence of the speed of external connections, I replaced receiving data with static data:

    /api/users.js
    // we can get data from any DB
    async function getDataFromDB() {
      return {
        page: 1,
        per_page: 6,
        total: 12,
        total_pages: 2,
        data: [
          {
            id: 1,
            email: "george.bluth@reqres.in",
            first_name: "George",
            last_name: "Bluth",
            avatar:
              "https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"
          },
          {
            id: 2,
            email: "janet.weaver@reqres.in",
            first_name: "Janet",
            last_name: "Weaver",
            avatar:
              "https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
          },
          {
            id: 3,
            email: "emma.wong@reqres.in",
            first_name: "Emma",
            last_name: "Wong",
            avatar:
              "https://s3.amazonaws.com/uifaces/faces/twitter/olegpogodaev/128.jpg"
          },
          {
            id: 4,
            email: "eve.holt@reqres.in",
            first_name: "Eve",
            last_name: "Holt",
            avatar:
              "https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"
          },
          {
            id: 5,
            email: "charles.morris@reqres.in",
            first_name: "Charles",
            last_name: "Morris",
            avatar:
              "https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"
          },
          {
            id: 6,
            email: "tracey.ramos@reqres.in",
            first_name: "Tracey",
            last_name: "Ramos",
            avatar:
              "https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"
          }
        ],
        ad: {
          company: "StatusCode Weekly",
          url: "http://statuscode.org/",
          text:
            "A weekly newsletter focusing on software development, infrastructure, the server, performance, and the stack end of things."
        }
      };
      /*
      return (await require("axios").get(`https://reqres.in/api/users?page=1`))
        .data;
      */
    }
    ....
    
  2. api , http

    /plugins/api-context.server.js
    export default (context, inject) => {
      inject("server", () => true);
      inject("api", async (controller, method, params) => {
        try {
          if (params && params.httpcall) {
            return await context.$axios["$" + (params ? "post" : "get")](
              "/api/" + controller + "/" + method,
              params
            );
          }
          let api = require("../api/" + controller.replace(/^\/+|\/+$|\.+/g, ""));
          return await api[method](params);
        } catch (e) {
          console.error(e);
          throw e;
        }
      });
    };
    
  3. index.vue fetch api 50

    /pages/index.vue
      async fetch() {
        let start = new Date();
        let promises = [];
        let callNum = 50;
        for (let i = 0; i < callNum; i++) {
          promises.push(this.$api("users", "index"));
        }
    
        let arr = await Promise.all(
          promises.map(async p => {
            return await p;
          })
        );
    
        let res = [];
        for (let r of arr) {
          res = res.concat(r);
        }
    
        this.users = res;
        this.fetchType =
          (this.$server && this.$server() ? "Server internal" : "Client http") +
          " API call";
        this.fetchTime = new Date() - start;
      },
    
  4. httpcall.vue fetch api 50 http

    /pages/httpcall.vue
    ...
          promises.push(this.$api("users", "index", { httpcall: true }));
    ...
        this.fetchType =
          (this.$server && this.$server() ? "Server http" : "Client http") +
          " API call";
    ...
    
  5. Now compare the execution time of rzdyw.sse.codesandbox.io

    Server internal API call rendering fetch time: 1ms
    time from 0ms to a maximum of 2ms

    rzdyw.sse.codesandbox.io/httpcall
    Server http API call rendering fetch time: 71ms
    time from 46ms to a maximum of 1059ms
    and several times the server generally crashed with an error

    RangeError
    Maximum call stack size exceeded

Here is a complete example - codesandbox.io/s/codesandbox-nuxt-rzdyw

Total Part 2


  • With minimal changes, you can speed up server rendering by more than 50 times, on a live example, rendering my page was accelerated by ~ 1.7 times
  • Node HTTP server resources significantly reduced
  • In an optimized way, Nuxt's only instance should withstand the load of small and medium-sized projects

All Articles