02.11.2018

Generating 100% static websites with an API and nuxt.js

Nuxt generated static page still try to fetch data from an API. Fetching the data first and saving them as JSON files helps.

Why

Simply put: It's fast and it looks good.

The longer version: Web applications have started to rule the internet. They look fancy, they have nice animations, but they have downsides too. Main downside: They can get slow very easily.

Nuxt alleviates this issue buy providing the function to statically render your vue web apps, so you can serve standard html files without issues.

ALMOST no issues, that is.

If you use an API as your data source it fetches the data beforehand and even renders it correctly. If you browse to the files directly by either opening the index.html or typing the url in your browser, they work, too. However, if you try to navigate to them throught nuxt-links, which enable awesome transitions and the "web application feel" they try to fetch the data again, which can result in issues. That's why this hack is needed.

How

To sum things up: Instead of fetching the API data in asyncData, you get all data beforehand, save it to JSON and require it in asyncData.

There were multiple issues on github before. User mingyjongo came up with a solution which then also got added to the nuxt boilerplate. All credit goes there.

For my case, this was almost it, but I have dynamic dat. I don't want to manually edit some file to loop through them. That's why I tweaked it a little. It works like this now:

  1. Get a list of all entries from the API
  2. Loop through the entries and fetch the detaildata for each entry
  3. Save the list and the details to JSON files in a directory structure that mirrors the url structure

This allows me to fetch each JSON file very easily in the nested pages.

It works beautifully.

Code Example

The code is directly from a recipe app I'm currently building. Change it to fit your code.

const fs = require('fs-extra')
const axios = require('axios')

const url = 'http://localhost:8000/recipes/'

module.exports = function fetchData() {
    //writeData writes the data to a file given the path
    //Same as in previous solution
    const writeData = (path, data) => {
        return new Promise((resolve, reject) => {
            try {
                fs.ensureFileSync(path)
                fs.writeJson(path, data, resolve(`${path} Write Successful`))
            } catch (e) {
                console.error(`${path} Write failed. ${e}`)
                reject(`${path} Write Failed. ${e}`)
            }
        })
    }

    const getData = async builder => {
        fs.emptyDir('static/data')
        console.log(`STARTING JSON BUILD FOR ${URL}...`)
        const fetcher = []

        // Fetch list from API

        const allRecipes = await axios.get(url)
        const basePath = `static/data/recipes`
        if (!fs.pathExistsSync(basePath)) fs.emptyDir(basePath)
        let fileName = `${basePath}/data.json`
        console.log(`PROCESSING ${fileName}...`)

        // Write list to file
        fetcher.push(writeData(fileName, { content: allRecipes.data }))

        // Loop through list
        for (let recipe of allRecipes.data) {
            let recipeUrl = url + recipe.slug

            // Fetch Detail Data
            let recipeResponse = await axios.get(recipeUrl)
            const path = `${basePath}/${recipe.slug}`
            if (!fs.pathExistsSync(path)) fs.emptyDir(path)
            fileName = `${path}/data.json`
            console.log(`PROCESSING ${fileName}...`)

            // Write Detail Data to file
            fetcher.push(writeData(fileName, { content: recipeResponse.data }))
        }
        return Promise.all(fetcher)
            .then(() => {
                console.log('JSON Build complete!')
            })
            .catch(e => {
                throw e
            })
    }

    // Run it before the nuxt build stage
    this.nuxt.hook('build:before', getData)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

This allows to fetch the data from the JSON files instead of the API:

async asyncData({params}) {
      let recipeJson = require(`~/static/data/recipes/${params.slug}/data.json`)
      return {
        recipe: recipeJson.content,
      }
  }
1
2
3
4
5
6