Sharing asynchronous data between Nuxt.js modules

If you’ve developed some sites in Nuxt.js, there’s a decent chance that you’ve had to asynchronously fetch data during the generation. However, there are often times when several modules such as the internal route module, sitemap module, or the RSS feed module require the same data from the database. Alas, there is no easy way to share the fetched data between the modules as they run in an isolated synchronous order. Or is there?

Behind the Localazy scenes

We’ve found ourselves in a spot where we generate dozens of dynamic routes and that kind of data has to be used for dynamic routes, sitemap generation and the RSS feed for our blog posts. In truth, it’s already quite challenging to generate all the dynamic routes with each website update itself. Since the Nuxt v2.13 there is a crawler option that enables detection of all the reachable dynamic routes by crawling through the statically generated content. However, I’ve had mixed success with it and doing it manually still seems like a safer option.

I will not go into much detail on how to fetch the data and generate all the dynamic routes. If you would like to know more, join us on Discord and reach out to me. Anyways, once you have the data, the trick to share them lies in having access to the filesystem through fse-extra within the Nuxt modules. So once you fetch the data, you can store them as a JSON file in the distribution folder.

Preparing the data

In our case, we begin in our custom module for data retreival and route generation. One of the final parts of it looks as follows:

const generateRoutes = (tablesToRoutes) => {
  const routes = [];
  Object.entries(tablesToRoutes).forEach((entry) => {
    const table = entry[0];
    entry[1].forEach((route) => {
      routes.push({
        route: table !== '' ? `/${table}/${route.route}` : `/${route.route}`,
        payload: route.payload
      });
    });
  });

  return routes;
};

Essentially, for each entry we resolve the proper route path, not important. The crucial part is this:

payload: route.payload

We’re storing the whole object that we’ve fetched under payload property. The route.payload is manufactured in one of the previous steps.

Once we have this gigantic array of route objects, we can step in into the final Nuxt’s generation hook and store it as JSON.

import fse from 'fs-extra'; // comes with Nuxt already

moduleContainer.nuxt.hook('generate:done', async () => {
    await fse.outputJson(`${this.nuxt.options.generate.dir}/routes.json`, routes, {
      spaces: 2,
      EOL: '\n'
    });
});

The above is quite self-explanatory, perhaps except for this.nuxt.options.generate.dir. When you read more about the Nuxt modules, you’ll find in which context is the code executed and this is just one of the properties you have access to by default.

Sharing the data

Now when you run npm run generate, you’ll find in your distribution folder (dist by default) a file called routes.json. The file contains an array of the data we’ve fetched in the module, in our case one entry may look like this:

...
{
  "route": "/blog/kotlin-mpp-and-http-with-wininet-api-on-windows",
  "payload": {
    "modified_on": "2020-09-09T12:57:04+00:00",
    "slug": "kotlin-mpp-and-http-with-wininet-api-on-windows",
    "priority": "0.7",
    "changefreq": "monthly",
    ...
  }
},
...

That’s everything we need in the other modules, so there is absolutely no need to refetch anything 😃 Let’s show how to utilize this in the commonly used sitemap module.

First, let’s modify the config in nuxt.config.js.

sitemap: {
    path: '/sitemap.xml',
    hostname: 'https://localazy.com',
    sitemaps: [
      {
        path: '/static.xml',
        routes: () => generateSitemap(this.default.generate.dir),
      },
  }

We’ll dive into the generateSitemap in a second, but notice the argument first. We’ll need to know where to find the JSON file and since we’re not creating a module, but only a function to generate routes for the sitemap module, we will not have access to nuxt.options as before. But we have access to it still within nuxt.config.js, so let’s just pass the path to the generated folder.

The definition of generateSitemap would vary for everyone, but the important bit are the first lines

import fse from 'fs-extra';

export default async function sitemapRoutes (generateDir) {
  const routesJson = await fse.readJson(`${generateDir}/routes.json`);

  return ...
}

As we know path to the JSON file, it’s enough to read it’s content. This grants us access to all the data we’ve prepared in the previously executed module and there is no need to re-fetch anything. This makes the generation considerably faster and puts less load on your database.

Obviously we could repeat this pattern for any other Nuxt module. Needless to say that with every additional module that would require such data, the benefits become even more obvious.