Academind Logo
Static Site Generation

Static Site Generation

Nuxt.js makes server-side rendering of Vue apps simple. But what if you don't want to run a Node.js server? There is solution for that...and it's not creating a SPA!

Created by Maximilian Schwarzmüller
#

Is Server-Side Rendering always the Perfect Solution?

Nuxt.js makes the creation of server-side rendered apps simple - it's actually built in. Just set the mode in the nuxt.config.js file to universal (that should be the default anyways) and you should get a server-side rendered app.

What does that actually mean though?

As explained earlier, this means that for each request sent to the server (i.e. for each page you want to load), the page will be pre-rendered dynamically ("on the fly") on the server. Hence the complete page, with all the content rendered, will be sent back.

The first page load is pre-rendered on the server.

Server-side rendering has a couple of advantages, one of the biggest one being improved SEO.

There's a downside, too, though: You need to run a Node.js server.

That's of course not necessarily a bad thing but it's certainly more complex (and probably also more expensive) than if you just would host a SPA on some static website host like AWS S3 or Firebase Hosting.

Of course you can build your app as a SPA instead of an universal app - simply switch the mode in nuxt.config.js from universal to spa. But you will then lose the pre-rendering of the pages and hence the improved SEO. So this doesn't seem ideal.

There is a third way though!

#

Rendering the App as a Static Website

Nuxt.js has a built-in static website generator. You can render your entire Nuxt.js app as a static website.

This means that all your pages/ routes will be pre-rendered once you execute a certain command and you can thereafter simply upload all your static files (just .html, .js and .css files as well as images) to your favorite static website hosting service. No Node.js server is required.

If you had the following pages structure:

pages / index.vue
users / index.vue

the following dist/ folder (the output directory) would be generated:

...
index.html
users/
index.html

So your .vue files would be rendered as .html files - just what would normally happen on the Node.js server.

Nuxt.js will execute asyncData and all server-side methods and code during the static-page-generation process, so you'll really get the same output as if the app was served via Node.js server.

So what is the magically command then?

It's npm run generate (inside of your Nuxt project folder).

#

Static Website Generation Gotchas

Generating the Nuxt app as a static website sounds great - but here are three issues you might face at some point:

  1. Your content changes quite frequently, hence your pre-rendered pages will be outdated

  • You have dynamic pages (e.g. _id/index.vue) to be generated - how does this work?

  • You got loads of pages to be generated

These are all valid concerns/ points. So let's tackle them one-by-one.

#

1. Your content changes quite frequently

This could be an issue if your content is created by users and changes every minute or something like this.

In general, you can of course implement some helper workflow which will simply run npm run generate (+ some deployment command) whenever your content (in your database or wherever it's stored) changes. The generation process is pretty fast and running such a build + deploy workflow is a pretty common thing in modern web development. You could use services like AWS Codebuild for that.

If your content changes multiple times per minute or if implementing such a workflow is not an option, static website generation might not be a viable option though.

#

2. You have dynamic pages

Did you notice something in my response to 1?

I mentioned that you could fetch your changes from a database and regenerate the pages with that updated data. Often if you have dynamic content in your app, you'll also have dynamic routes though. Something like pages/users/_id/index.vue for example.

How does Nuxt generate such dynamic pages though? It doesn't know what a valid id would be.

The solution can be found (and implemented in) the nuxt.config.js file.

There, you can add the generate property to the exported object and explicitly inform Nuxt.js about which routes it should generate.

generate: {
routes: function () {
return [
'/users/12',
'/users/13'
]
}
}

With this code, we give Nuxt.js the missing information about our dynamic route. We tell it to generate two pages - one with id being 12 and one with id being 13.

We would end up with a dist/ folder like this:

...
users/
index.html
12/
index.html
13/
index.html

That's better but still has one big disadvantage: You need to know all dynamic parameters in advance - otherwise you can't add them to your nuxt.config.js file. Let's fix this later but have a look at argument 3 first.

#

3. You got loads of pages to be generated

Maybe your content doesn't update that often, hence generating a static website would be an option. But you could have the case that you got 1,000s of pages that need to be generated. This could be the case for an e-commerce website for example.

The good news is: Nuxt.js is really fast at generating pages, so you should be able to generate even that many pages relatively quick. I personally noticed a weird bug though: For many pages, the generation process can slow down quite a bit - up to a level where it even quits unexpectedly.

It can easily be fixed by changing the generation configuration though. In nuxt.config.js, add minify: false to the generate property:

generate: {
minify: false,
routes: [ ... ]
}

No worries, your code still gets minified and optimized but this configuration speeds up the build process immensely. Sou you'll really need an app with many 1,000s of pages to hit a limit I guess.

#

Generating Dynamic Routes

I already touched on how you can generate dynamic routes/ pages - by defining the routes which should be generated in the nuxt.config.js file.

The problem just is: You need to define all routes in advance. If you got truly dynamic content, you don't know your user ids etc during development.

The good thing is: You can fetch your data from a server within your nuxt.config.js file. So you can even turn your config into something dynamic - isn't that awesome?

You can fetch your data from a server and generate the route-config dynamically. So a dynamic config for dynamic routes for a static website.

Whenever you run npm run generate with such a setup, your code in nuxt.config.js will be executed, data will be fetched and your routes will be configured. Thereafter, the generation process will run and it'll take your dynamically created route config into account.

Here's an example:

generate: {
routes: function () {
return axios.get('https://my-api/users')
.then((res) => {
return res.data.map((user) => {
return '/users/' + user.id
})
})
}
}

As you can see, we make a request to our API to fetch all our users. The route config (for the generate functionality) is thereafter generated from that fetched data. Nuxt.js will wait for the config to be finished before it starts the page generation.

#

Avoiding Unnecessary API Requests

With the ability to generate the page-generation config dynamically, we got a truly dynamic and powerful way of generating a static website.

There's one downside though. At least a potential one.

We hit our API a lot!

If you got asyncData calls in your pages/users/_id/index.vue file, you'll execute that code (and hence send a request) for every page that gets generated.

If you got 20 users - and therefore 20 routes that need to be generated - you'll send 21 requests.

1 request to get the user ids (for the route generation config) and 20 requests for the per-page data.

That's not ideal. It hammers your API whenever you generate your pages.

It's also unnecessary because you can probably fetch all the data you need - the user ids and the per-page data - in one single API request: The request you do from within the nuxt.config.js file.

Nuxt.js even got you covered in this case. You can pass a payload to each of your generated pages.

generate: {
routes: function () {
return axios.get('https://my-api/users')
.then((res) => {
return res.data.map((user) => {
return {
route: '/users/' + user.id,
payload: user
}
})
})
}
}

In this example, the user object is passed as a payload to each generated pages/users/_id/index.vue page. Please note that you now don't just create an array of strings representing your page paths anymore. Instead, you create an array of JavaScript objects, where each object holds a route and (optionally) a payload property.

In your page components, you can consume the payload in the asyncData method - you can extract it from the context object you receive:

export default {
asyncData(context) {
// check if you got a payload first
if (context.payload) {
// extract the user object passed from nuxt.config.js
return { userData: context.payload.user }
} else {
// if you got no context, go ahead and make the API request
return axios.get('my-api.com/users').then(...)
}
}
}

With this final building block, you got all you need to generate dynamic pages statically without hammering your API. Awesome!

Of course you can read more about this in the official docs.