Vue.js, like many other frameworks such as React, Angular, etc., is a client-side framework, meaning the webpage is rendered by running JavaScript on the client side. These apps are commonly called single-page applications, or SPAs.
When an SPA is loaded on the browser, the server only sends the basic HTML without any rendered content. It makes another request to fetch the JavaScript bundle. JavaScript then runs in the browser to render the content. When we view the page source, we see something like the below:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>My Blog</title> </head> <body> <div id="app"></div> <script src="/js/app.js"></script> </body> </html>
See the problem there? When search engine bots crawl your page, they only get this bare HTML without any content. There is no data for the bots to use to index your page.
Well, SPAs have been around for quite a while now, and Google does say their crawlers can index SPAs now. There is quite a lot of uncertainty there. How long do the crawlers wait on your page? What if your bundle size is too big? what if, due to some error, the page couldn’t render properly? Does it retry?
Let’s assume it was successfully able to render the client-side code and index it properly. Does it mean your page is now optimized for search? There are many criteria that contribute page rank, and page download speed is among the most important. An SPA is generally slower on first content paint compared to old-school static HTML/CSS pages as there is the overhead of making an Ajax call to fetch the bundle and render it.
We have come a long way from those static HTML/CSS pages, so obviously, we can’t go back there again. Those methods had their own problems — each request has to go to the server to fetch specific and common data, download new stylesheets for different pages each time the user navigates, etc.
Is there a solution that utilizes the best features of both methods, that is having great SEO and also be super fast like a SPA? Well, hello SSR!
Server-side scripting is a technique used in web development that involves employing scripts on a web server that produce a fully rendered page. This page is then returned to the client application. SSR produces faster page loads since all the content is already rendered in the server. Let’s build one such application with Nuxt.js
Run the following command to create a Nuxt app:
npx create-nuxt-app nuxt-blog-seo
You get the following options. My setup looks like the image below:
If you are new to the Nuxt framework, then there a few things Nuxt does differently compared to Vue:
pages
directory to get the routing structure (it does automatic code splitting 🙌). You can add an external routing file, but it’s not required.<router-link>
, Nuxt uses a special tag <nuxt-link>
.Now go to the pages
folder and modify the index.vue
file with the following code:
<template> <div class="container"> <h1>welcome to my page</h1> <div> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum ex modi sapiente amet temporibus exercitationem qui nihil eius, nam sequi sint iste nostrum corrupti, similique in vel impedit inventore id! </div> </div> </template> <script> export default {} </script>
Run the application using the npm run dev
command. Open the webpage and go to view page source, and voilà! We see our content in the page source.
Let’s add one more page and a link to the index.vue
file:
<!-- Article.vue --> <template> <div class="container"> <h1>A dummy article</h1> <div> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum ex modi sapiente amet temporibus exercitationem qui nihil eius, nam sequi sint iste nostrum corrupti, similique in vel impedit inventore id! </div> </div> </template>
Now let’s add a link to this page in our main page:
<nuxt-link to=”/Article”> My Article </nuxt-link>
Save it and run the app again and you’ll be able to navigate to this page. Did you notice that the page opens instantly, just like how an SPA would work? After the first page load, Nuxt behaves like an SPA. View the source again and we can see the full source of the Article.vue
page, too! This is because Nuxt creates a static version of the website (for static assets).
In your Article.vue
file, instead of using dummy hardcoded data, let’s fetch it from the web this time. For this purpose, I’ll make use of json placeholder api
and axios
. We added the Axios module when we created the application; it can be accessed in our Vue components like a plugin:
this.$axios .get('http://jsonplaceholder.typicode.com/posts/1') .then((res) => { return { fetchedData: res.data } })
Where do we add this code? We could add this in the created()
hook, but created()
runs only on the client side, and that’s not what we want.
asyncData
asyncData
tells Nuxt to render that particular part of the code on the server. When it runs on the server, our Vue component isn’t initialized yet; thus, we cannot use this
or any methods here. However, we receive Nuxt’s context
object, which has all that data.
<template> <div class="container"> <h1>{{ fetchedData.title }} test</h1> <div> {{ fetchedData.body }} </div> </div> </template> <script> export default { asyncData(context) { return context.$axios .get('http://jsonplaceholder.typicode.com/posts/1') .then((res) => { return { fetchedData: res.data } }) } } </script>
Open the page again and check the page source. We see the server has already rendered the data. Great! 🎉
Nuxt internally runs a real-time Node server. Thus, it’s able to pre-render the pages before it’s even sent to the client. To host this application, we need a server capable of running Node.js.
Does that mean we can no longer host it on static hosting providers like Netlify? Well, yes — that’s the sacrifice we need to make. But we’ll come back to this problem later.
There is no need to install Vuex since Nuxt automatically does it when it sees content in the store
folder.
I want to show the username in both the homepage and the article page. We need to fetch this from the server. Instead of fetching it in both places, let’s fetch it once and store it in Vuex.
Create a user module in Vuex by creating a new file, user.js
, inside the store
folder:
export const state = () => ({ userName: 'default' }) export const mutations = { updateUserName(state, value) { state.userName = value } } export const actions = { getUserName(context) { return this.$axios .get('https://jsonplaceholder.typicode.com/users/1') .then((res) => { context.commit('updateUserName', res.data.name) }) } }
Here, I am fetching the userName
from the server. Let’s display this on both pages:
<div>Name: {{ $store.state.user.userName }}</div>
We could call the action getUserName
in the asyncData
, method which runs on server, but Nuxt provides a special action method called nuxtServerInit
.
nuxtServerInit
methodThis method is called automatically by Nuxt on the server. We can use this to populate the store on the server side. This method can only be used in the primary module, so let’s create an index.js
file in the store
folder:
export const actions = { async nuxtServerInit(vuexContext) { await vuexContext.dispatch('user/getUserName', { root: true }) } }
Now the action getUserName
will be automatically called, and userName
will be populated on the server side. Similarly, we can call any number of actions from different modules inside the nuxtServerInit
.
Meta tags are one of the most important factors that impact SEO. Nuxt uses vue-meta
internally to generate the contents of the <head>
tag, such as page title, meta tags, etc.
So what’s special here? We can use vue-meta
in vanilla Vue.js, too. In the case of Vue.js, the meta tags are populated at the same time the JavaScript renders the page, so the bots may or may not pick up the meta tags.
In such cases where the meta tags are populated based on the subsequent Ajax call, we can also see the page title dynamically changing after the response is received. The page source will not have meta tags. This is pretty bad for SEO.
On the other hand, Nuxt pre-renders the meta tags, too! Even if there is a subsequent Ajax call, we can call that in asyncData
or in nuxtServerInit
, which are executed in the server. So in all cases, the bots get the updated meta tags when they crawl our page! Let’s see this in action.
Let’s add page title and meta tags to our article page:
export default { asyncData(context) { return context.$axios .get('http://jsonplaceholder.typicode.com/posts/1') .then((res) => { return { fetchedData: res.data } }) }, head() { return { title: this.fetchedData.title, meta: [ { hid: 'description', name: 'description', content: this.fetchedData.body } ] } } }
After reloading the page, view the page source and we can see both of them reflected there.
Nuxt can generate a static version of the website that is SEO-friendly and does not require us to run a real-time Node server backend to get all the benefits. We can just host it on static servers like any other Vue application and still have all the benefits of SEO.
To build in static mode, use the following command — it generates the code for all possible routes in the dist
directory:
npm run generate
Nuxt is designed with SEO in mind. With Nuxt, we can take control of many factors that impact SEO and page ranking. Nuxt fills the gaps and shortcomings of SPAs and makes the process of creating an SEO-friendly application easy and enjoyable.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
9 Replies to "How Nuxt.js solves the SEO problems in Vue.js"
Great article 🙂 One question though…. does this mean that because we generate a static version of the site at the end we can in fact host it on netlify? And on load for the user it makes all api calls from the server as well?
Thanks bud
Nuxt.js is never a static website. It can’t be hosted on Netlify but it can be hosted on Heroku or Firebase. Indeed, it is the case that “on load for the user it makes all api calls from the server”.
Thanks Brendin!
Nuxt.js application either in SPA mode or Static mode after building, both can be hosted on Netlify or similar static hosting service. Even Vue.js applications can be hosted on Netlify.
For your second question, Nuxt will create static HTML files for all the routes when you run `npm run generate`. If you are wondering how would it work for dynamic routes. ( say `/posts/:id` )
Because Nuxt doesn’t know what is the value of `id` would be here.
So in `nuxt.config.js` you can pass the routes that has to be statically generated.
“`
//nuxt.config.js
generate: {
routes () {
return [‘/posts/aslkdja9’]
}
}
“`
You can still make dynamic API calls in a static generated application as well. In your case if you want to make user specific API calls. That can be done too. Let’s say you have some user specific data that will be fetched only after login? Then won’t be a part of Static HTML (You won’t be able to see that in `view page source`)
Static method is generally not used for large applications, It might make the configuration quite complex.
To know more https://nuxtjs.org/api/configuration-generate/
Hope this answers your question 🙂
It depends on what mode you build the application. If you build in `Universal` mode, Then definitely you would need a server capable of running a Node server like Heroku. If you build in `SPA` or generate `static` using `npm run generate` then you can host in static hosting services like Netlify, AWS S3 etc.
If you don’t want a SSR web site, what’s the point of using Nuxt? Better to just stay with Vue in its regular form.
Nuxt has other good features too, Here is one very detailed article explaining about it. https://www.vuemastery.com/blog/why-nuxtjs-is-the-perfect-framework-for-building-static-websites/
All good, but TTFB is always bad. It takes more than 800ms on basic site. GT metrics always shows red on it!
TTFB also depends on your server speed and its location. Here is one Nuxt.js site ( https://alpiq.oc-i.eu/ ) I have worked on which has just 26ms TTFB from the nearest Geo location to the hosting server.
Test results here: https://gf.dev/tests/gq8j3kgj6rh
It does change depending on the testing tool, but mostly stays below 500ms for most Geo locations.
Is there any way to migrate a vue 2 app into a nuxt js app??