Edmund Ekott Frontend engineer who specializes in building complex UIs with JavaScript and CSS.

Gridsome vs. Nuxt.js: Comparison and tutorial with examples

10 min read 2859

Gridsome vs. Nuxt.js: Comparison and Tutorial With Examples

Vue.js has grown in popularity over the past two years, with nearly 180,000 stars on GitHub at the time of writing. This increasing adoption has compelled developers around the world to build myriad tools and frameworks around Vue.js, inlcuding Nuxt.js and Gridsome.

In this guide, we’ll compare Gridsome vs. Nuxt.js, two frameworks built on Vue.js that you can use to build fast JAMstack applications. We’ll evaluate each in terms of the developer experience, general use and best practices, structure, and community adoption. To show how Nuxt.js and Gridsome work, we’ll build an example blog with both frameworks.

Here’s what we’ll cover:

To follow along with this Gridsome vs. Nuxt comparison and tutorial, you should have the following:

  • Node >= v8.3 and NPM installed
  • Yarn package manager installed: npm install -g yarn (Gridsome recommends using Yarn)
  • Basic knowledge of JavaScript
  • Basic knowledge of Vue.js
  • Gridsome CLI installed: npm install -g @gridsome/cli.
  • Knowledge of GraphQL (not compulsory)
  • Basic knowledge of the command line

What is Gridsome?

Gridsome is a data-driven static site generator that generates HTML files from local files, CMSs, and external APIs. HTML files are initially loaded in the browser as static files and then hydrate into fully powered Vue.js apps. This improves SEO while providing all the benefits of Vue. The data is prefetched and stored in a GraphQL data layer.

Here’s an overview of Gridsome’s directory structure.

What is Nuxt.js?

Nuxt.js is a very powerful Vue.js framework that allows you to build modern web applications any way you choose. Nuxt.js gives you the flexibility to build web applications either as single-page, server-side rendered, or statically generated.

Check the Nuxt.js documentation for an overview of the Nuxt directory structure.

Gridsome example: Building a blog app

To show how Gridsome works — and establish a frame of reference to compare to Nuxt.js — let’s explore how to build a blog with Gridsome.

To create a new Gridsome project, run:

We made a custom demo for .
No really. Click here to check it out.

gridsome create my-gridsome-blog

Open the newly created folder in your code editor and run gridsome develop to start the development server.

In your browser, open http://localhost:8080 and you should see your awesome — albeit unfinished — blog.

Gridsome Blog Example

Building a UI in Gridsome

The /src directory is where you’ll be working with the .vue files. Starting with the /pages directory, every .vue file in the directory becomes a page in the browser, so the contents of About.vue will be on /about in the browser.

There are two ways to create pages in Gridsome: you can build file-based pages with the .vue files or you can use the pages API (more on this later).

Paste the code snippet below inside the <Layout></Layout> component in /pages/index.vue to replace the initial content.

<h1 class="blog-title">
  Welcome my awesome blog
</h1>
<p>I love Vue.js so I write about it.</p>
<div class="articles-list">
  <h2 class="article-list__section-heading">New Articles</h2>
  <!-- articles will be listed here -->
</div>

Before we move on, you’ll need to import the CSS for the blog globally. Create a file using this directory structure:

/src/assets/css/main.css

Paste the contents of this Gist in the main.css file and then import it at the top of the main.js file, which is in the root of the /src directory:

import '~/assets/css/main.css`

The main.js file is used to import global styles and scripts, You’ll also notice that we have access to the Vue instance in the exported function, we can use that to install Vue.js plugins, register global components (just like the default layout component) and directives.

Generating blog content

Gridsome also has a number of reusable packages called plugins. These plugins can be used to implement some custom functionality in your apps. There are over 180 plugins currently listed on the Gridsome plugins page.

We’ll use the @gridsome/source-filesystem plugin to transform the content we write in markdown and load them in a GraphQL layer so we can query in our components.

The plugin also requires @gridsome/transformer-remark to work with markdown files.

To install the filesystem source plugin:

yarn add @gridsome/source-filesystem

To install the transformer:

yarn add --dev @gridsome/transformer-remark

To configure the plugin, modify the gridsome.config.js file to look like this:

module.exports = {
  siteName: 'Gridsome',
  plugins: [
    {
      use: '@gridsome/source-filesystem',
      options: {
        typeName: 'BlogPost',
        path: './blog/**/*.md',
      },
    },
  ],
  templates: {
    BlogPost: '/blog/:title',
  },
};

The gridsome.config.js file is where we register Gridsome plugins and configure the project:

  • typeName in the plugin options is the name we give the GraphQL collection for our blog posts and template (a .vue fill we create momentarily in /src/templates)
  • path is where the plugin should look for markdown files to generate content
  • templates is the object that defines what the routes for template files should look like; :title, in this case, is the title of the blog post, which is dynamic.

Create a blog folder in the root of the project, add a markdown file, hello.md, and paste the contents below:

---
title: Hello World
description: This is the first article for my awesome blog
date: 2021-01-14
published: true
---
# A h1 header
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
look like:
  * this one
  * that one
  * the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> Block quotes are
> written like so.

## An h2 header
------------
Here's a numbered list:
 1. first item
 2. second item
 3. third item

```js
  // loop
  for(i= 0; i<=4; i++){
    console.log(i)
  }
```

Next, create a BlogPost.vue file in /src/templates. This file is responsible for rendering data from the GraphQL collection that has the type BlogPost, which is the same name we set in the gridsome.config.js file. The value of typeName in gridsome.config.js must match the file created in /src/templates.

Restart your server and open http://localhost:8080/___explore in your browser. You should see a GraphQL playground.

Paste the query below in the left tab and click the “play” button.

query {
  allBlogPost (filter: { published: {eq: true } } ) {
    edges {
      node {
        path,
        title,
        date(format: "DD MMM YYYY"),
        timeToRead,
        content,
      }
    }
  }
}

The query above fetches all the data associated with type BlogPost and filters by the ones that have published set to true.

Listing blog posts

Now that we have content, it’s time to display it.

Update the index.vue file in /src/pages with the code snippets below.

Add the query below just before the <script> tag in the file:

<page-query>
query {
  allBlogPost (filter:{ published: {eq: true } } ) {
    edges{
      node{
        path,
        title,
        date(format: "DD MMM YYYY"),
        timeToRead,
        description,
        content,
      }
    }
  }
}
</page-query>

The snippet above is the same query as the one we ran earlier in the GraphQL playground, except this time, the data is available as a computed property called $page, which we can access inside <script> and <template>.

Next, paste the code in the following snippet below the <h2> in the same file:

<div class="article-list__item" v-for="({ node: article }, index) in $page.allBlogPost.edges" :key="index">
  <h3>
    <g-link :to="article.path">{{ article.title }}</g-link>
    <!-- g-link is the Gridsome equivalent of router-link for Vue, but with some magic  ✨ -->
  </h3>
  <p>
    Published on <strong>{{ article.date }}</strong>
  </p>
  <p>{{ article.description }}</p>
</div>

You should now see your new article listed.

Displaying a post

Now it’s time to create a .vue file in /src/templates.

Create a file called BlogPost.vue in src/templates and paste the content of the snippet below:

<template>
  <div>
    <Layout>
      <h1>{{ $page.post.title }}</h1>
      <p>
        Published on <strong>{{ $page.post.date }}</strong
        >, <strong>{{ $page.post.timeToRead }} min.</strong> read
      </p>
      <div v-html="$page.post.content"></div>
    </Layout>
  </div>
</template>

<page-query>
query blogPost ($path: String!) {
  post: blogPost (path: $path) {
    id
    title
    content
    date (format: "D MMMM YYYY")
    timeToRead
  }
}
</page-query>

<script>
export default {
  metaInfo() {
    return {
      title: this.$page.post.title,
    };
  },
};
</script>

Gridsome automatically passes the URL for this article called path to the query and that’s used to fetch the remaining data about this post, the data is now displayed on the page inside the <template> tag.

When you click on your article from the homepage, you should now see the content of your new article. Congratulations! You just built a blog with Gridsome.

Nuxt.js example: Building a blog app

Now that we’ve walked through building a blog with Gridsome, let’s do the same with Nuxt.js and then compare the developer experience.

To set up a new Nuxt project, run the following command and follow the steps from the screenshot below (do not select any Nuxt.js modules during setup; just skip that part):

npx create-nuxt-app my-nuxt-blog

Follow these steps:

Creating a Nuxt.js Project

Open the newly created folder in your code editor and run this command:

npm run dev

Open http://localhost:3000 in your browser to see your new blog.

Nuxt.js Blog Example

Building a UI in Nuxt.js

We’ll be working with the /pages directory first. Every .vue file in this directory is converted to a page in the browser, so index.vue will become /. This is called file-system routing in Nuxt.

Delete the <style></style> tag and modify the content of the <template> in /pages/index.vue to this:

<div class="container">
  <h1 class="blog-title">Welcome my awesome blog</h1>
  <p>I love Vue.js so I write about it.</p>
  <div class="articles-list">
    <h2 class="article-list__section-heading">New Articles</h2>
    <!-- articles will be listed here -->
  </div>
</div>

Next, create a CSS file with with this directory structure assets/css/main.css and paste the contents of this Gist in the file:

Import the CSS globally in the nuxt.config.js file:

export default = {
   css: ['~/assets/css/main.css'],
}

You should now see the changes in your browser.

Generating blog content

Nuxt.js has a large collection of reusable packages called modules that can be used to add custom features to your applications. We’ll use the nuxt-content module.

Install the module:

npm install @nuxt/content # or using yarn, anyone is fine

Register the module for your app inside nuxt.config.js:

{
  modules: [
    '@nuxt/content'
  ],
  content: {
    // Options
  }
}

Create a new folder, /content, in the root of the project. In that folder, create a file called hello.md and paste the snippet below:

---
title: Hello World
description: This is the first article for my awesome blog
published: true
---

# A h1 header
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
look like:
  * this one
  * that one
  * the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> Block quotes are
> written like so.

## A h2 header
------------
Here's a numbered list:
 1. first item
 2. second item
 3. third item

```js
  // loop
  for(i= 0; i<=4; i++){
    console.log(i)
  }
```

The content between the opening and closing (---) is known as front matter. You can create as many more markdown files as you’d like.

Listing blog posts

Now that we have content to work with, we can start creating pages to list all our blog posts as well as display the content of a single post.

Update the <script></script> tag of the /pages/index.vue file with the snippet below:

export default {
  async asyncData({ $content, params, error }) {
    const path = '/' // to fetch all the content
    const articles = await $content(path)
      .where({ published: { $eq: true } })
      .fetch()
      // eslint-disable-next-line node/handle-callback-err
      .catch((err) => {
        error({ statusCode: 404, message: 'Page not found' })
      })
    return {
      articles,
    }
  },
}

The Nuxt.js content module features a MongoDB-like query API for fetching data, so requests we make look similar to MongoDB queries.

The snippet above fetches all the content we have by specifying a root path (/) and adding a filter to only fetch content that have the published key set to true in the YAML frontmatter.

Next, update the <template> just below the comment to render the articles:

<div
  v-for="(article, index) in articles"
  :key="index"
  class="article-list__item"
>
  <h3>
    <nuxt-link :to="`/article${article.path}`">{{ article.title }}</nuxt-link>
    <!-- nuxt-link is the Nuxt equivalent of router-link for Vue -->
  </h3>
  <p>
    Published on
    <strong>{{
      new Date(article.createdAt).toLocaleDateString()
    }}</strong>
  </p>
  <p>{{ article.description }}</p>
  <hr />
</div>

Check out the app in your browser. It should look like this:

Rendering Blog Articles in Nuxt.js

Next, we’ll demonstrate how to render a single article.

Displaying a post

In the /pages directory, create a file with this structure: /article/_path.vue. Paste the content of the snippet below into the newly created _path.vue file:

<template>
  <article class="container">
    <h1>Title: {{ article.title }}</h1>
    <hr />
    <br />
    <br />
    <nuxt-content :document="article" />
  </article>
</template>

<script>
export default {
  async asyncData({ $content, params, error }) {
    const { path } = params
    const article = await $content(path)
      .fetch()
      // eslint-disable-next-line node/handle-callback-err
      .catch((err) => {
        error({ statusCode: 404, message: 'Page not found' })
      })
    return {
      article,
    }
  },
}
</script>

The snippet above does two things: fetches the data from an article by the path and renders it in the template.

In the <script> tag of the component, we use the asyncData hook to fetch data server-side, since we do not have access to this yet. It’s worth mentioning that asyncData can only be used in pages.

The first parameter in the hook is the Nuxt context object, and we’re destructuring properties we need in the page. We use $content from the content module to make queries and params is an alias for $route.params on the client side. We take the path property from params and use that query for the article’s data.

In the <template> tag, we render the article by passing it to the global <nuxt-content/> component that was added by the content module, the component knows how to render the data passed to it already — as you may have noticed, the article’s content is not in HTML format.

Voila! your new Nuxt.js blog is ready.

Gridsome vs. Nuxt.js: Which is better?

Now that’s we’ve demonstrated how to build a blog with Gridsome and Nuxt.js, let’s compare the developer experience of using each framework.

Starting a new project

Gridsome and Nuxt.js offer different approaches to scaffolding a new project. With Nuxt.js, you’d use the create-nuxt-app tool to start a new project. Nuxt gives you a lot of options to customize the project before installing dependencies.

To start a new project with Gridsome, on the other hand, you’d use the Gridsome CLI. With Gridsome, you have the option to use a starter template.

Development modes

You can pretty much build any type of web application you want with Nuxt.js, including:

  • Single-page applications (SPAs), which are rendered in the browser. Usually, JavaScript is sent down to the browser and executed to generate a full-fledged app
  • Server-side rendered (SSR) **applications, which are generated on the server every time the browser makes a request. As you probably noticed when building with Nuxt.js earlier, the data was fetched on the server first and the template was compiled before sending everything back to the browser.
  • Statically generated (SG) applications, which are generated only once on the server during deployment. All data is fetched beforehand and HTML files are generated accordingly. When the browser makes a request, the HTML file and related assets are sent down

With Gridsome, you can only build statically generated applications, which is the framework’s sole purpose.

Fetching data

With Nuxt.js, you don’t need to learn a new tool with a different syntax, just JavaScript. With Gridsome, you do need some knowledge of GraphQL. You can learn about it as you build and reference the documentation in your app’s explorer.

The repositories for both the Gridsome example and the Nuxt.js example used in this tutorial are available on GitHub.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Edmund Ekott Frontend engineer who specializes in building complex UIs with JavaScript and CSS.

Leave a Reply