Building Your Website With Gridsome - A Complete Guide.

If you are planning to build a website, chances are that you have come across the term "Static Site Generators". They are essentially tools that take your data in the form of Markdown, JSON, CSV and APIs and convert them into static HTML pages. There are plenty of static site generators available, but if you like working with Vue.js, then Gridsome is the best option there is.

#Installation

Before working with Gridsome, make sure you have Node.js version 8.3 or higher installed. Let's begin by opening the command prompt and installing Gridsome's command line interface.

npm install --global @gridsome/cli

Then create a project with the name of your choice, I will go with 'mysite'.

gridsome create mysite

Switch to the newly created folder.

cd mysite

And, start your local server.

gridsome develop

Enter http://localhost:8080/ in your URL bar and you should see mysite's default home page.

#Creating Pages

The root of the project is where our main configuration file gridsome.config.js resides. We will come back to this later when configuring our plugins. The src folder is where we will get coding. Let's start with the pages directory. You should see a file named About.vue here, pointing to the page available at the URL /about. Index.vue will be our homepage.

Click on the About.vue file and let's edit some of the content inside the <p> tag. Also, change the title property in the metaInfo object, found in the script tag.

<template>
  <Layout>
    <h1>About us</h1>
    <p>I'm building a website with Gridsome.</p> 
  </Layout>
</template>

<script>
export default {
  metaInfo: {
    title: 'This is my About Page' 
  }
}
</script>

Go to /about and you should see the changes reflected. The metaInfo.title is a placeholder for the title of the document. Let's add another page and call it FrequentlyAskedQuestions.vue and add this bit of code there.

<template>
  <Layout>
    <h1>FAQ</h1>
    <p>What's the meaning of life?</p>
  </Layout>
</template>

<script>
export default {
  metaInfo: {
    title: 'This is the FAQ page'
  }
}
</script>

How do I access the newly created page? FrequentlyAskedQuestions.vue will be available at the URL /frequently-asked-questions. This was just a silly test to see how the file name translates to the URL.

#Building The Blog

Let's create a few mock blogs. In the root directory, create a folder called blog.

|-blog
|-node_modules
|-src
  |-assets
  |-components
  |-pages
  |-templates
  |-main.js
|-gridsome.config.js
|-gridsome.server.js

Our blog content will be stored in markdown files with .md extension. Let's start with our first blog and name it first-blog.md. The information between the two --- symbols will be the meta-data useful for querying the blog posts. If you need to generate some random text, checkout this fun little website called Jeffsum.com

---
title: First Blog!
author: John Doe
tags: ["quotes", "jeff goldblum", "actor"]
dateCreated: 2019-06-22
dateModified: 2019-06-22
---
You're a very talented young man, with your own clever thoughts and ideas. Do you need a manager? You know what? It is beets. I've crashed into a beet truck. God creates dinosaurs. God destroys dinosaurs. God creates Man. Man destroys God. Man creates Dinosaurs.

Just my luck, no ice. This thing comes fully loaded. AM/FM radio, reclining bucket seats, and... power windows. You're a very talented young man, with your own clever thoughts and ideas. Do you need a manager? God creates dinosaurs. God destroys dinosaurs. God creates Man. Man destroys God. Man creates Dinosaurs.

Similarly, create another blog for testing purposes. Let's try the URL /first-blog and we have a 404 error. Still a few things to do before we can start viewing the content we just created. The first of which is to install plugins, so that we can start fetching the content in the markdown files with GraphQL. In the terminal type

npm install @gridsome/source-filesystem @gridsome/transformer-remark

Open the file gridsome.config.js in the root directory and register the source-filesystem plugin in the plugins array. We will also change the siteName while we are here.

module.exports = {
  siteName: 'Mysite',
  plugins: [
    //paste this
    {
      use: '@gridsome/source-filesystem',
      options: {
        path: 'blog/**/*.md',
        typeName: 'BlogPost', // remember this name
        route: '/blog/:slug'  // customise to your liking. 
      }
    },
  ]
}

Run gridsome develop again and let's see what GraphQL can do. Open the link http://localhost:8080/___explore and enter this query

query {
	allBlogPost {
    edges {
      node {
        id
        title
        author
        path
      }
    }
  }
}

The magical thing about the graphql playground is the prompts provided on keyboard input, allowing you to explore the schema. Edges refers to a set of related posts and a Node points to each individual post. A node can then be queried for specific fields like title, author and path --- based on the meta-data provided in the markdown files. The id value is autogenerated by GraphQL. The result should look something like this.

{
  "data": {
    "allBlogPost": {
      "edges": [
        {
          "node": {
            "id": "6c3864d08405eb663f2b7812116eca02",
            "title": "Second Blog!",
            "author": "Jane Doe",
            "path": "/blog/second-blog"
          }
        },
        {
          "node": {
            "id": "06dde81847ade81ddea7e09170d0373c",
            "title": "First Blog!",
            "author": "John Doe",
            "path": "/blog/first-blog"
          }
        }
      ]
    }
  }
}

The next step is to see this GraphQL query result rendered on the website. The home page is where we would want all our blog posts to be listed. So let's open the src/pages/Index.vue file. You will see a <Layout> component, which is registered as a global component in main.js. The layout component is used to house fixed components such as headers and footers, while the dynamic parts are slotted inside the <Layout> component itself.

Let's add the GraphQL query first. Queries have their very own <page-query> tag.

<page-query>
  query {
    blogs: allBlogPost {
      edges {
        node {
          id
          title
          author
          path
        }
      }
    }
  }
</page-query>

The result of the query is stored in a $page property. You can rename the collection allBlogPost with a name of your liking. I have chosen blogs for this example. Loop over the data with the v-for directive to list out all the blog posts created. The :key attribute in v-for is used as a unique identifier, to keep track of all the virtual DOM nodes we are generating. The autogenerated id will be a perfect value to feed the :key attribute. g-link renders to a href anchor link, with some additional functionalities like prefetching pages. The path field will be the slugified version of the title.

<template>
  <Layout>
    <article 
        v-for="edge in $page.blogs.edges" 
        :key="edge.node.id">
        <h2>
          <g-link :to='edge.node.path'>
            {{edge.node.title}}
          </g-link>
        </h2>
        <small>{{edge.node.author}}</small>
        <hr>
    </article>
  </Layout>
</template>


<page-query>
  query {
    blogs: allBlogPost {
      edges {
        node {
          id
          title
          path
          author
        }
      }
    }
  }
</page-query>

Now that we have our home page, let's build our blog page for showcasing individual blog posts. All our single post views will be written in the templates folder. Here, we will create a new file named BlogPost.vue. Recall that while configuring gridsome.config.js, I had asked you to remember the typeName. This will be the name of your GraphQL schema as well as your template name. Let's go to the GraphQL playground at http://localhost:8080/___explore once again and explore.

query Blog ($path: String){
  post: blogPost (path: $path) {
    title
    author
    content
  }
}

# in QUERY VARIABLES
{
  "path": "/blog/first-blog"
}

If you have more than one query in the GraphQL playground, make sure you name your query like 'Blog' in the above example.

Here we are retrieving an individual blog post based on its path. Our query here can't depend on a static path argument and it will need to handle dynamic variables. First we will check if our $path variable is of the type String. Then we will pass the variable $path as an argument to the query. Press the play button and you should see the title, author and content for the first blog we have created. Let's add this to our website.

In BlogPost.vue

<template>
  <Layout >
    <main class="blog"> 
      <h1>{{$page.post.title}}</h1>
      <small>{{$page.post.author}}</small>
      <div class="blog__content" v-html="$page.post.content">
      </div>
    </main>
  </Layout>
</template>

<page-query>
  query Blog($path: String){
    post: blogPost(path: $path){
      title
      author
      content
    }
  }
</page-query>

To display the content, Gridsome uses the Vue's v-html directive. Our blog pages are now ready.

#Creating Author and Tag Pages

A big part of our website is done. But, what if you wanted to build an author page, listing out all the posts written by an author. Or a tags page, filtering all the posts by a tag. There are two ways of doing this. The first method is similar to the way we built the blogs. Create an author folder at the root level and create a .md file for every author on the website. Similarly, you could do this for tags. But, the process will be cumbersome and keeping track of all those files is going to be a headache. What if the tags and author pages can be generated based on the meta-data provided in the blogs?

The @gridsome/source-filesystem plugin previously installed takes care of this for us. However, it will need some configuration. Open the gridsome.config.js file. Here we will add a refs object that will create references to our blogposts with the fields provided.

module.exports = {
  siteName: 'mysite',
  plugins: [
    {
      use: '@gridsome/source-filesystem',
      options: {
        path: 'blog/**/*.md',
        typeName: 'BlogPost', 
        route: '/blog/:slug',
        refs: {
          tags: {
            typeName: 'Tag',
            route: '/tag/:slug',
            create: true
          },
          author:{
            typeName: 'Author',
            route: '/author/:slug',
            create: true
          }
        },
      }
    }
  ]
}

The create property when set to true will automatically create the content pages so that you don't have to do it manually. Terminate the batch job and run gridsome develop in the console once again. You should see this error: Error: Field "author" of type "Author" must have a selection of subfields. Did you mean "author { ... }"?. This is because the author field now is a node and has subfields that we can query. Remove the author field from the page-query in both BlogPost.vue and Index.vue and try to run gridsome develop once again. The error should no longer be visible. Open the GraphQL playground and explore the subfields.

query Blog ($path: String){
	blogPost (path: $path){
    title
    author {
      id
      path
    }
    tags {
      id
      path
    }
  }
}
# in QUERY VARIABLES
{
  "path": "/blog/first-blog"
}

Now we have access to the author's name with the id field and a path to the author's page.

{
  "data": {
    "blogPost": {
      "title": "First Blog!",
      "author": {
        "id": "John Doe",
        "path": "/author/john-doe"
      },
      "tags": [
        {
          "id": "quotes",
          "path": "/tag/quotes"
        },
        {
          "id": "jeff goldblum",
          "path": "/tag/jeff-goldblum"
        },
        {
          "id": "actor",
          "path": "/tag/actor"
        }
      ]
    }
  }
}

Make the changes to BlogPost.vue.

<template>
  <Layout >
    <main class="blog"> 
      <h1>{{$page.post.title}}</h1>
      <g-link :to=$page.post.author.path>
        <small>
          {{$page.post.author.id}}
        </small>
      </g-link>
      <div class="blog__content" v-html="$page.post.content">
      </div>
    </main>
  </Layout>
</template>

<page-query>
  query Blog($path: String){
    post: blogPost(path: $path){
      title
      content
      author {
        id
        path
      }
    }
  }
</page-query>

Similarly, in Index.vue we will add id and path subfields to both author and tags.

<template>
  <Layout>
    <article 
        v-for="edge in $page.blogs.edges" 
        :key="edge.node.id">
        <h2>
          <g-link :to="edge.node.path">
            {{edge.node.title}}
          </g-link>
        </h2>
        <small>
          <g-link :to="edge.node.author.path">
            {{edge.node.author.id}}
          </g-link>
        </small>
        <div>
            <span v-for="(tag, index) in edge.node.tags" 
                :key="tag.id">
                <g-link :to="tag.path">
                  {{tag.id}}
                </g-link>
                <!-- We will add a comma separator for the tags -->
                <span v-if="index + 1 < edge.node.tags.length">
                  ,  
                </span>
            </span>
        </div>
        <hr>
    </article>
 </Layout>
</template>

<page-query>
  query {
    blogs: allBlogPost {
      edges {
        node {
          id
          title
          path
          author {
            id
            path
          }
          tags {
            id
            path
          }
        }
      }
    }
  }
</page-query>

We will create two new templates for author and tags, just like how we did for the blogs. Based on the typeName provided in gridsome.config.js, let's name the files Author.vue and Tag.vue.

In Author.vue add the following:

<template>
  <Layout>
    <article 
        v-for="edge in $page.blogs.edges" 
        :key="edge.node.id">
        <h2>
          <g-link :to="edge.node.path">
            {{edge.node.title}}
          </g-link>
        </h2>
        <small>
          <g-link :to="edge.node.author.path">
            {{edge.node.author.id}}
          </g-link>
        </small>
        <div>
            <span v-for="(tag, index) in edge.node.tags" 
                :key="tag.id">
                <g-link :to="tag.path">
                  {{tag.id}}
                </g-link>
                <span v-if="index + 1 < edge.node.tags.length">
                  ,  
                </span>
            </span>
        </div>
        <hr>
    </article>
 </Layout>
</template>

<page-query>
query BlogsByAuthor ($id: String) {
  blogs: allBlogPost (filter: {author: {eq: $id}}) {
    edges {
      node {
        title
        path
        author {
          id
          path
        }
        tags {
          id
          path
        }
      }
    }
  }
}
</page-query>

Here we will filter allBlogPost to give us only those posts that contain the argument id. Just like the eq operator, there are plenty of other operators that you can use - listed here .

We will do the same for Tag.vue, but with a slight change to the filters. As tags is an Array Field, we will use the contains operator. This will check if the argument is present in the list of tags provided in the blog.

<template>
  <Layout>
    <article 
        v-for="edge in $page.blogs.edges" 
        :key="edge.node.id">
        <h2>
          <g-link :to="edge.node.path">
            {{edge.node.title}}
          </g-link>
        </h2>
        <small>
          <g-link :to="edge.node.author.path">
            {{edge.node.author.id}}
          </g-link>
        </small>
        <div>
            <span v-for="(tag, index) in edge.node.tags" 
                :key="tag.id">
                <g-link :to="tag.path">
                  {{tag.id}}
                </g-link>
                <span v-if="index + 1 < edge.node.tags.length">
                  ,  
                </span>
            </span>
        </div>
        <hr>
    </article>
 </Layout>
</template>

<page-query>
query BlogsByTags($id: String) {
  blogs: allBlogPost (filter: {tags: {contains: [$id]}}) {
    edges {
      node {
        title
        path
        author {
          id
          path
        }
        tags {
          id
          path
        }
      }
    }
  }
}
</page-query>

And there you have it, we now have our author and tag pages.

#Creating Reusable Components

You may have noticed that our Vue template syntax is quite similar in Index.vue, Author.vue and Tag.vue. Let's use the Do not repeat yourself principle and create a reusable component called BlogList.vue in the src/components directory.

<template>
  <div>
    <article 
        v-for="edge in $page.blogs.edges" 
        :key="edge.node.id">
        <h2>
          <g-link :to="edge.node.path">
            {{edge.node.title}}
          </g-link>
        </h2>
        <small>
          <g-link :to="edge.node.author.path">
            {{edge.node.author.id}}
          </g-link>
        </small>
        <div>
            <span v-for="(tag, index) in edge.node.tags" 
                :key="tag.id">
                <g-link :to="tag.path">
                  {{tag.id}}
                </g-link>
                <span v-if="index + 1 < edge.node.tags.length">
                  ,  
                </span>
            </span>
        </div>
        <hr>
    </article>
 </div>
</template>

<script>
export default {
    name: 'blogList',
    props: ['queryList'] // queryList data received from its parent
}
</script>

In pages/Index.vue, we will import the component BlogList and register it. Then pass the data $page.posts.edges as props to the component.

<template>
  <Layout>
    <BlogList :queryList=$page.blogs.edges></BlogList>
  </Layout>
</template>

<script>
import BlogList from '~/components/BlogList.vue'

export default {
  components: {
    BlogList //Register component BlogList
  }
}
</script>

Follow the same directions for Author.vue and Tag.vue. Now, we have a reusable component that's easy to update.

#Deploying On Netlify

And for our final step, we will deploy our project to Netlify. There are two ways to go about it. The first one is by running the command gridsome build. The command will generate a dist folder in the project's root directory. All you have to do is drag and drop the folder, and your site will be live in a custom Netlify subdomain in no time.

The other way would be to create a GitHub repository, connect it to Netlify and take advantage of Netlify's continuous deployment feature. Whenever you push your project's changes to your GitHub repository, Netlify triggers the build command and the changes will be reflected on your site accordingly. 

After logging into Netlify, click on the New site from Git button. You will be directed to a page with a series of three steps. The first of which will be to select a Git provider. Once the authorisation from Git is complete, your second step will be to select the project's repository. The third and final step will be to enter build settings. For Gridsome, it will be: 

Build Command: gridsome build

Publish directory: dist

Click on the Deploy site button. The build should take a couple of minutes, after which your site should be live for the world to see.