Search code examples
javascriptnuxt.jsvuexssgstatic-site-generation

How to store nuxtjs dynamically generated routes in vuex store


I'm trying to leverage nuxtjs SSG capabilities by creating a static web site where the pages content and navigation are fetched from an API.

I already found my way around on how to dynamically generate the routes by defining a module where I use the generate:before hook to fetch the pages content and routes. When creating the routes I store the page content as the route payload. The following code does just that and works as intended.

modules/dynamicRoutesGenerator.js

const generator = function () {
  //Before hook to generate our custom routes
  this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
    generator.generateRoutes(await generateDynamicRoutes())
  })
}

let generateDynamicRoutes = async function() {
  //...
  return routes
}

export default generator

Now the problem I'm facing is that I have some navigation components that need the generated routes and I was thinking to store them into the vuex store.

I tried the generate:done hook but I don't know how to get the vuex store context from there. What I ended up using was the nuxtServerInit() action because as stated in the docs:

If nuxt generate is ran, nuxtServerInit will be executed for every dynamic route generated.

This is exactly what I need so I'm trying to use it with the following code:

store/index.js

export const actions = {
  nuxtServerInit (context, nuxtContext) {
    context.commit("dynamicRoutes/addRoute", nuxtContext)
  }
}

store/dynamicRoutes.js

export const state = () => ({
  navMenuNivel0: {}
})

export const mutations = {
  addRoute (state, { ssrContext }) {
    //Ignore static generated routes
    if (!ssrContext.payload || !ssrContext.payload.entrada) return
    //If we match this condition then it's a nivel0 route
    if (!ssrContext.payload.navMenuNivel0) {
      console.log(JSON.stringify(state.navMenuNivel0, null, 2));
      //Store nivel0 route, we could use url only but only _id is guaranteed to be unique
      state.navMenuNivel0[ssrContext.payload._id] =  {
        url: ssrContext.url,
        entrada: ssrContext.payload.entrada,
        navMenuNivel1: []
      }
      console.log(JSON.stringify(state.navMenuNivel0, null, 2));
      //Nivel1 route
    } else { 
      //...
    }
  }
}

export const getters = {
  navMenuNivel0: state => state.navMenuNivel0
}

The action is indeed called and I get all the expected values, however it seems like that with each call of nuxtServerInit() the store state gets reset. I printed the values in the console (because I'm not sure even if it's possible to debug this) and this is what they look like:

{}                                                                                                                                                                                                                     
{                                                                                                                                                                                                                      
  "5fc2f4f15a691a0fe8d6d7e5": {
    "url": "/A",
    "entrada": "A",
    "navMenuNivel1": []
  }
}
{}                                                                                                                                                                                                                     
{                                                                                                                                                                                                                     
  "5fc2f5115a691a0fe8d6d7e6": {
    "url": "/B",
    "entrada": "B",
    "navMenuNivel1": []
  }
}

I have searched all that I could on this subject and altough I didn't find an example similar to mine, I put all the pieces I could together and this was what I came up with.

My idea was to make only one request to the API (during build time), store everything in vuex then use that data in the components and pages.

Either there is a way of doing it better or I don't fully grasp the nuxtServerInit() action. I'm stuck and don't know how to solve this problem and can't see another solution.

If you made it this far thanks for your time!


Solution

  • I came up a with solution but I don't find it very elegant.

    The idea is to store the the API requests data in a static file. Then create a plugin to have a $staticAPI object that expose the API data and some functions.

    I used the build:before hook because it runs before generate:before and builder:extendPlugins which means that by the time the route generation or plugin creation happen, we already have the API data stored.

    dynamicRoutesGenerator.js

    const generator = function () {
      //Add hook before build to create our static API files
      this.nuxt.hook('build:before', async (plugins) => {
        //Fetch the routes and pages from API
        let navMenuRoutes = await APIService.fetchQuery(QueryService.navMenuRoutesQuery())
        let pages = await APIService.fetchQuery(QueryService.paginasQuery())
        //Cache the queries results into staticAPI file
        APIService.saveStaticAPIData("navMenuRoutes", navMenuRoutes)
        APIService.saveStaticAPIData("pages", pages)
      })
      
      //Before hook to generate our custom routes
      this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
        console.log('generate:before')
        generator.generateRoutes(await generateDynamicRoutes())
      })
    }
    
    //Here I can't find a way to access via $staticAPI
    let generateDynamicRoutes = async function() {
      let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
      //...
    }
    

    The plugin staticAPI.js:

    import APIService from '../services/APIService'
    
    let fetchPage = function(fetchUrl) {  
      return this.pages.find(p => { return p.url === fetchUrl})
    }
    
    export default async (context, inject) => {
      //Get routes and files from the files
      let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
      let pages = APIService.getStaticAPIData("pages")
      //Put the objects and functions in the $staticAPI property
      inject ('staticAPI', { navMenuRoutes, pages, fetchPage })
    }
    

    The APIService helper to save/load data to the file:

    //...
    let fs = require('fs');
    
    let saveStaticAPIData = function (fileName = 'test', fileContent = '{}') {
      fs.writeFileSync("./static-api-data/" + fileName + ".json", JSON.stringify(fileContent, null, 2));
    }
    
    let getStaticAPIData = function (fileName = '{}') {
      let staticData = {};
    
      try {
          staticData = require("../static-api-data/" + fileName + ".json");
      } catch (ex) {}
    
      return staticData;
    }
    
    module.exports = { fetchQuery, apiUrl, saveStaticAPIData, getStaticAPIData }
    

    nuxt.config.js

    build: {
      //Enable 'fs' module
      extend (config, { isDev, isClient }) {
         config.node = { fs: 'empty' }
      }
    },
    plugins: [
      { src: '~/plugins/staticAPI.js', mode: 'server' }
    ],
    buildModules: [
      '@nuxtjs/style-resources',
      '@/modules/staticAPIGenerator',
      '@/modules/dynamicRoutesGenerator'
    ]