Jamstack Application With Gatsby and Bugfender

Jamstack Application With Gatsby and Bugfender

EngineeringJavaScriptReact

Jamstack is a modern web development architecture based on client-side JavaScript, reusable APIs, and prebuilt Markup. It is not yet a full technology stack like MEAN, MERN or LAMP. Rather, it is an architectural concept built using JavaScript, API and Markup.

Before we look at how to use Jamstack in more detail, let’s examine its component parts and what they represent:

Jamstack is base on JavaScript, API and Markup

Jamstack applications are hosted, in their entirety, on a Content Delivery Network (CDN) or an Application Delivery Network (ADN). Everything is stored in GIT, and automated builds are provided with a workflow when developers push the code. The pre-built markup is automatically deployed to the CDN/ADN.

These characteristics provide a bunch of significant benefits:

  • The whole process is practically serverless, removing a lot of failure points and potential security exploits.
  • The pre-built content served via CDN provides super-fast user experiences.
  • The reduced complexity of development lowers costs.
  • The Develop => Build => Test => Deploy Cycle is very well-managed.

How to Build Jamstack Apps

Today, there are myriad tools, frameworks, library and services available to build and manage Jamstack applications. Among the most popular are static site generators (SSGs) which facilitate the construction of pre-built markups, as well as CDNs/ADNs. These SSGs come with generous price plans to deploy and host the applications, and offer both services and APIs.

One of the most popular members of the current generation of SSGs is Gatsby, a React-based framework specifically designed to create prebuilt markups. As well as offering a plethora of plug-in ecosystems, Gatsby is also hooked up to a vibrant community support network.

In this post, we’re going to show you how to build Gatsby with Bugfender, our remote logging service which allows users to collect logs for everything that happens in their application. It’s easy to integrate Bugfender with web apps and there are lots of SDKs available to integrate with mobile apps, too.

Ok, enough of the pitch. Let’s get moving!

What are we building today?

We’re going to build a basic blogging site called The Purple Blog. In doing so, we will see that Gatsby can build such sites in double-quick time with the help of GraphQL and markdown files. During the build process, we will integrate Bugfender to collect application logs, create automatic user feedback, issues and crash reports, and analyze them.

When we’re done, the Gatsby and Bugfender-powered blog site may look like this:

TL;DR

If at any point of time you want to look into the source code or play around with the blog site, here are the links:

GitHub Repository: https://github.com/atapas/gatsby-bugfender

and

Demo Link: https://gatsby-bugfender.netlify.app/

Create the Project Structure with Gatsby

We will use a Gatsby starter to create the initial project structure. To do this, you need to install Gatsby CLI globally, and the best way to do this is by opening a command prompt and running this command:

npm install -g gatsby-cli

Now, use the following command to create a Gatsby project structure.

gatsby new purple-blog https://github.com/gatsbyjs/gatsby-starter-default

We are using the gatsby-starter-default starter project template to create our blogging tool, as this will initiate the project with all required libraries and dependencies.

Once done, you will see a project folder called purple-blog has been created. Go to that folder and open a command prompt there. Type the following command to run the app in the development mode:

gatsby develop

Now, you should be able to access the interface using http://localhost:8000/.

First screen – Gatsby Default

Set Up Bugfender

To kick things off, simply create an account with Bugfender. Once logged in, create a Bugfender application for web apps using the Web SDK option. You can follow this step-by-step guide to create a Bugfender application, and you will find an API key ready for you. Keep it safe.

Once you have created your app, the Bugfender dashboard will enable you to keep track of logs, issues, feedback and crashes. This is how my dashboard looks:

BugFender Dashboard with an App

Gatsby and Bugfender

A gatsby-based application can run in two different environments.

  • gatsby develop: A development environment with hot reloading enabled. In this environment, all browser-specific APIs like localstorage, and objects like window work well.
  • gatsby build with gatsby serve: This is the environment to build the app to produce deployable artifacts; once you have created them, you can run the app from the built artifacts. In this environment, the browser-specific APIs and objects will not work as the environment is based on nodejs. For example, the window object is not available in the nodejs and we may end up getting an error like:
Possible error while building a Gatsby app

On the other hand, Bugfender is a client-specific tool and it depends on browser-specific objects like window. Hence there is a chance that a Bugfender API that works well in the gatsby develop environment may fail in the gatsby build. We need to provide some configurations along with code changes to allow the Bugfender APIs to work with both the Gatsby environments.

Install Bugfender SDK

Open a command prompt and the root of the project folder and use this command to install the Bugfender SDK:

yarn add @bugfender/sdk # Or, npm i @bugfender/sdk

Configure gatsby-node for Bugfender

Open the file named gatsby-node.js and add the following content:

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
   if (stage === "build-html") {
     /*
       * During the build step, @bugfender will break because it relies on
       * browser-specific APIs. Fortunately, we don’t need it during the build.
       * Using Webpack’s null loader, we’re able to effectively ignore @bugfender
       * during the build. (See src/utils/bugfender.js to see how we prevent this
       * from breaking the app.)
       */
     actions.setWebpackConfig({
       module: {
         rules: [
           {
             test: /@bugfender/,
             use: loaders.null(),
           },
         ],
       },
     })
   }
 }

A few things are going on here. We are telling gatsby that Bugfender is a client-specific thing and it is not required at the build stage. Using Webpack’s null loader, we’re able to effectively ignore Bugfender during the build. The loader checks for an npm package that starts with the name @bugfender, then ignores it. Simple!

Create a Utility for Bugfender APIs

Next, we will create a utility file to wrap the Bugfender APIs so that they can be ignored at the build stage. You can do this by creating a folder called utils under src, then creating a file called bugfender.js under src\\utils with the following content:

import { Bugfender } from '@bugfender/sdk'
const isBrowser = typeof window !== "undefined"

const GatsbyBugfender = {
    init: () => {
        if (!isBrowser) {
            return
        }
        Bugfender.init({
            appKey: '',
        })
    },

    log: (...messages) => {
        if (!isBrowser) {
            return
        }
        Bugfender.log(messages.join( ))
    },

    error: (...messages) => {
        if (!isBrowser) {
            return
        }
        Bugfender.error(messages.join( ))
    },

    sendUserFeedback: (key, value) => {
        if (!isBrowser) {
            return
        }
        Bugfender.sendUserFeedback(key, value)
    },

    sendIssue: (key, value) => {
        if (!isBrowser) {
            return
        }
        Bugfender.sendIssue(key, value)
    },

    sendCrash: (key, value) => {
        if (!isBrowser) {
            return
        }
        Bugfender.sendCrash(key, value)
    }
}

export default GatsbyBugfender;

We are actually taking care of a few things here:

  • First, we’re checking that the app is running in the browser mode or nodejs mode.
  • We’re allowing the calling of a Bugfender API if we’re sure it is running in the browser mode.
  • The init function uses the API_KEY you noted down while setting up Bugfender a while ago.
  • You can add all the Bugfender APIs or just the ones you need.

Use the API function from the Utility

Now we will be able to initialize and use Bugfender in the Gatsby code without any issues.

Let’s start by taking a look at a single usage. Open the file, src/pages/index.js and import the GatsbyBugfender utility we have created:

import GatsbyBugfender from '../utils/bugfender' 

Call the init method after all the imports:

// all imports
// ....
GatsbyBugfender.init();

const IndexPage = ({data}) => (
// ....

Now you can call the Bugfender APIs in the Gatsby app from any of the pages, components or templates. Here is an example:

if (posts.length > 0) {
    GatsbyBugfender.log(`${posts.length} posts found in the repository`)
    GatsbyBugfender.sendUserFeedback('Posts created', 'Default Posts created Successfully!')
  } else {
    GatsbyBugfender.sendIssue('No Posts Found')
  }

The Blogging App

Now we will focus on building The Purple Blog.

To do so, we can take advantage of Gatsbyjs’s well-established ecosystem, provided by an amazing community is constantly writing plug-ins and making them available to install.

We need two specific plug-ins for our app.

  • gatsby-source-filesystem: This helps us source data from a local file system. Our blogging app is going to source the data from local markdown (*.md) files, and this plug-in turns them into File nodes – which can then be converted into different data types using transformer plug-ins.
  • gatsby-transformer-remark: As we will be using the markdown files as the data source, we need to convert the File node into a MarkdownRemark node so that we can query the HTML representation of the markdown. We will use the gatsby-transformer-remark plug-in for that purpose.

Install Dependencies

You will most probably have installed the gatsby-source-filesystem plug-in when creating the basic project structure. Let us now install the rest of the dependencies:

yarn add gatsby-transformer-remark lodash react-feather # Or npm i ...

We have created our project from the starter project gatsby-starter-default. It should have installed gatsby-source-filesystem already. You can check it by finding it in the package.json file. If you don’t find it installed, please install it manually using the yarn or npm command as shown above.

Also note that we are installing the lodash and react-feather libraries for the JavaScript object, using array operations and free icons respectively.

Gatsby Configuration File

Open the gatsby.config.js file and perform the following changes:

  1. Declare the source and transform plug-in configurations so that the build process knows where to load the source files from and transform them. Add these to the plugins array. Here we are telling Gatsby to expect the data source files from the _data folder.

plugins: [
// ... omitted other things unchanged

{
  resolve: `gatsby-source-filesystem`,
  options: {
    name: `markdown-pages`,
    path: `${__dirname}/_data`,
  },
 },
 `gatsby-transformer-remark`,

// ... omitted other things unchanged
]

  1. Change the value of the title property of the siteMetadata object to something meaningful. We will provide the name of our app here, i.e. The Purple Blog.
module.exports = {
  siteMetadata: {
    title: `The Purple Blog`,

  // ... omitted other things unchanged

Gatsby, Markdown and GraphQL

Now, we will create the data source files and query them so that we can use the result on the React components.

Create a folder called _data at the root of the project folder, and create a markdown file with the following format:

---
date: 2020-05-18
title: What is Life?
tags:
  - soft-skill
  - spirituality
  - life
  - science
author: Matt Demonic
---

> Taken from [Wikipedia]() to dmonstrate an example.

Life is a characteristic that distinguishes physical entities that have biological processes, such as signaling and self-sustaining processes, from those that do not, either because such functions have ceased, or because they never had such functions and are classified as inanimate.

In the past, there have been many attempts to define what is meant by "life" through obsolete concepts such as odic force, hylomorphism, spontaneous generation and vitalism, that have now been disproved by biological discoveries. Aristotle is considered to be the first person to classify organisms. Later, Carl Linnaeus introduced his system of binomial nomenclature for the classification of species. Eventually new groups and categories of life were discovered, such as cells and microorganisms, forcing dramatic revisions of the structure of relationships between living organisms. Though currently only known on Earth, life need not be restricted to it, and many scientists speculate in the existence of extraterrestrial life. Artificial life is a computer simulation or human-made reconstruction of any aspect of life, which is often used to examine systems related to natural life.

Death is the permanent termination of all biological processes which sustain an organism, and as such, is the end of its life. Extinction is the term describing the dying out of a group or taxon, usually a species. Fossils are the preserved remains or traces of organisms.

If you are new to markdown file structure, you can learn it here. As the purpose of our app is to create blog articles, we have defined the structure of an article here. Notice that we have the publication date, title, author, tags and finally the content of the article. You can create as many such files as you wish.

At this stage, start the Gatsby development server using the gatsby develop command if it is not running already. If it is running, please restart it. Open a browser tab and try the URL http://localhost:8000/___graphql. It will open an editor for you to create the desired queries to query data from the source files.

The image below shows three panels. The first is to select the attributes to form a query. The second shows the query formed and allows you to change things manually. The last panel is to show the results.

Gatsby GraphQL editor for Query

The query formed here is a GraphQL query. We will use queries like this in the reactjs components using Gatsby GraphQL support, which is provided out-of-the-box.

Gatsby Template and Dynamic Page Creation

You may recall that we have included tags among the properties for the blog article. This means that we can show tags for an article and allow blog readers to use them to filter articles.

For example, when we click on the tag javascript, we want to list all the articles that have the same tag.. The same applies for any other tags we add.

Also, notice that the URL changes when we click on a tag to filter the articles.

Tag Flow – Page changes

With Gatsbyjs you can also create pages, and each of them will create a route (a unique URL) for you automatically.

A page can be created statically simply by creating a file under the src/pages directory. The name of the file then becomes the unique URL name. You can also create a page dynamically using templates: this is an extremely powerful concept very apt for the tag use-case we have seen just now.

We have to create a page dynamically for each of the tags, so that it also creates a unique URL, and when an article title is clicked. We have to show the full article content to the user and the unique part of the URL is called slug.

To create pages dynamically, open gatsby-node.js and add these lines at the top of the file:

const path = require(`path`);
const _ = require("lodash");
const { createFilePath } = require(`gatsby-source-filesystem`);

Here we are importing required libraries to create the setup for the dynamic page creation.

Next, we will override two Gatsby methods, onCreateNode and createPages.

Override onCreateNode

We will override this method to create a new node field called slug, so that we can use this node in our query later. To create slug, add this code snippet after the require statements:

//... all require statements

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
} 

Override createPages

Add this code snippet after the onCreateNode method:

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  
  // 1 - Query to all markdown files
  const result = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            fields {
              slug
            }
            frontmatter {
              title
              tags
              date
            }
          }
        }
      }
    }
  `);

  const tagSet = new Set();
  
  // 2 - Iterate through the nodes and create pages
  result.data.allMarkdownRemark.edges.forEach((edge) => {
    
    // 3 Create page for each of the node
    createPage({
      path: edge.node.fields.slug,
      component: path.resolve(`./src/templates/blog-post.js`),
      context: {
        // Data passed to context is available
        // in page queries as GraphQL variables.
        slug: edge.node.fields.slug,
      },
    });

    // 4- Generate a list of tags
    if (edge.node.frontmatter.tags) {
      edge.node.frontmatter.tags.forEach(tag => {
        tagSet.add(tag);
      });
    }

    // 5- Generate pages for each of the tags
    tagSet.forEach(tag => {
      createPage({
        path: `/tags/${_.kebabCase(tag)}/`,
        component: path.resolve(`./src/templates/tagged-post.js`),
        context: { tag }
      });
    });
  })
}

A few things are going on here:

  1. First, we need to create a query that results out in listing all the markdown files. Here we are interested in the title, tags, date and the newly created field, slug.
  2. The query returns an array of transformed file nodes, each of which contains the information we intended to make queries for. We loop through the array to create the required pages.
  3. Create pages for each of the nodes. Here, we are telling the Gatsby build process to use the blog-post.js file under the src/templates folder to create pages. These pages will be used when our users click on the article title to get to article details.
  4. Next, we loop through the tags of all the articles and create a set (which is the unique collection in JavaScript) of unique tags.
  5. Create a page for each of the tags. Here, we are telling the Gatsby build process to use the tagged-post.js file under the src/templates folder to create pages. These pages will be used when our users click on the tag of an article to filter out the articles with the same tag.

We will create both the template files shortly.

Create Templates and Components

Now we will create a reactjs component to render the article list. Simply create a file called PostList.js under the folder src/components with the following content. This is a simple react component which loops through each of the post articles and renders them.

import React from "react"
import TagCapsules from "./TagCapsules"
import { Link } from "gatsby"
import { User } from 'react-feather'

import GatsbyBugfender from '../utils/bugfender'

const Post = props => (
  <div>
    <Link 
        to={props.details.fields.slug}
        style={{textDecoration: 'none', color: '#960798',fontWeight: '600', fontSize: '25px'}}>
        {props.details.frontmatter.title}
    </Link>
    <div style={{padding: '3px'}}>
      <User color='purple' size={16} />{' '}
      <span>{props.details.frontmatter.author}</span>
      {", "}
      <span>on {props.details.frontmatter.date}</span>
    </div>
    <p>{props.details.excerpt}</p>
    <TagCapsules tags={props.details.frontmatter.tags} />
  </div>
)

export default (props) => {
  let posts = props.data.allMarkdownRemark.edges
  if (posts.length > 0) {
    GatsbyBugfender.log(`${posts.length} posts found in the repository`)
    GatsbyBugfender.sendUserFeedback('Posts created', 'Default Posts created Successfully!')
  } else {
    GatsbyBugfender.sendIssue('No Posts Found')
  }
  return (
    <div>
      {posts.map((post, index) => (
        <Post details={post.node} key={post.node.id} />
      ))}
    </div>
  )
}

Next, create a file called TagCapsules.js under the same folder. This is a component to create representation for the tags in the article list page.

import React from "react"
import _ from "lodash"
import { Link } from "gatsby"

import GatsbyBugfender from '../utils/bugfender'

import styles from "./TagCapsules.module.css"

const Tag = props => {
  const tag = props.tag
  GatsbyBugfender.log(`Recieved Tag ${tag}`)

  return (
    <li>
      <Link className={styles.tag} to={`/tags/${_.kebabCase(tag)}`}>
        {tag}
      </Link>
    </li>
  )
}

const Tagcapsules = props => {
  const tags = props.tags
  GatsbyBugfender.log(`Recieved ${tags.length} tags`)
  return (
    <ul className={styles.tags}>
      {tags && tags.map(tag => <Tag tag={tag} key={tag} />)}
    </ul>
  )
}

export default Tagcapsules

We will be using some styling to make the tags look better. To do this, create a file called TagCapsules.module.css under the same folder, with the following content:

.tags {
    list-style: none;
    margin: 0 0 5px 0px;
    overflow: hidden; 
    padding: 0;
  }
  
  .tags li {
    float: left; 
  }
  
  .tag {
    background: rgb(230, 92, 230);
    border-radius: 3px 0 0 3px;
    color: rgb(255, 255, 255);
    display: inline-block;
    height: 26px;
    line-height: 26px;
    padding: 0 20px 0 23px;
    position: relative;
    margin: 0 10px 10px 0;
    text-decoration: none;
  }
  
  .tag::before {
    background: #fff;
    border-radius: 10px;
    box-shadow: inset 0 1px rgba(0, 0, 0, 0.25);
    content: '';
    height: 6px;
    left: 10px;
    position: absolute;
    width: 6px;
    top: 10px;
  }
  
  .tag::after {
    background: #fff;
    border-bottom: 13px solid transparent;
    border-left: 10px solid rgb(230, 92, 230);
    border-top: 13px solid transparent;
    content: '';
    position: absolute;
    right: 0;
    top: 0;
  }
  
  .tag:hover {
    background-color: rgb(143, 4, 224);
    color: white;
  }
  
  .tag:hover::after {
     border-left-color: rgb(143, 4, 224); 
  }

Now we will create both the template files. Create a folder called templates under the src folder and create the file blog-post.js, using the content below. Please note the query at the end of the file: it queries the title and the content for a post article, and renders that. This is the page to show when a user clicks on the title of an article to see the details.

import React from "react";
import { graphql } from "gatsby";
import SEO from "../components/seo"
import Layout from "../components/layout";

export default ({ data }) => {
  const post = data.markdownRemark
  return (
    <Layout>
        <SEO title={post.frontmatter.title} />
        <div>
            <h1>{post.frontmatter.title}</h1>
            <div dangerouslySetInnerHTML={{ __html: post.html }} />
        </div>
    </Layout>
  )
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

Now it’s time to create another template. Create a file called tagged-post.js under src/template folder, using the following content. Here we make a query for all the posts that match a particular tag. Then we pass the matched post array to the PostList component we have already created.

import React from "react";
import { graphql } from "gatsby";
import Layout from "../components/layout";
import PostList from '../components/PostList';

export default ({data}) => {
    console.log(data);
    return (
      <Layout>
        <div>
          <PostList data={data} />
        </div>
      </Layout>
    )
};

export const query = graphql`
  query($tag: String!) {
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { tags: { in: [$tag] } } }
      ) {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM, YYYY")
            tags
          }
          fields {
            slug
          }
          excerpt
        }
      }
    }
  }
`

Now, the last thing is to change the index.js page so that our home page shows all the articles. Open the index.js file and replace the content with the following. Here, we are querying all the post articles and passing the array as a props to the PostList component.

import React from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"
import { graphql } from "gatsby"

import GatsbyBugfender from '../utils/bugfender'

import PostList from '../components/PostList'

GatsbyBugfender.init({
  appKey: 'YOUR_BUGFENDER_APP_KEY',
});

const IndexPage = ({data}) => (
  <Layout>
    <SEO title="All Posts" />
    <PostList data={data} />
  </Layout>
)

export default IndexPage

export const GET_ALL_POSTS = graphql`
  {
    allMarkdownRemark (
      sort: { fields: [frontmatter___date], order: DESC }
      ){
      edges {
        node {
          id
          frontmatter {
            title
            tags
            date(formatString: "DD MMMM, YYYY")
            author
          }
          html
          excerpt
          fields {
            slug
          }
        }
      }
    }
  }
`

All you need to do is replace the YOUR_BUGFENDER_APP_KEY in the above code with the app key you created while setting up the Bugfender app. Cool, right?

Now, restart gatsby develop if it is running already. You can access the app with the URL http://localhost:8000 to see it running successfully.

The completed application

Deploy it on Netlify

The app is running successfully on localhost. Let’s make it accessible to users by hosting it on a CDN. While doing that, we will also set up a continuous integration and deployment (CI/CD) so that a build-and-deploy kicks off with the code changes pushed to the Git repository.

The Netlify platform enables us to do this easily. Create an account with Netlify and log in to the application using the web interface. Now follow the steps mentioned below to deploy the app on Netlify with the CI/CD enabled by default.

Make sure to commit and push all the source code to the GitHub repository. You can create a new site with Netlify simply by selecting your GitHub repository.

Select the GitHub Repository

In the next step, provide the build settings as shown in the image below.

Build Settings

A build will be initiated automatically once the steps are completed. Please wait for the build to finish successfully. In case of an issue, you can consult the build logs for more details.

Build in-progress

Netlify creates a site for you with a random name. However, you can change it as per your choice based on availability.

Give a suitable site name

That’s it! Now the app will be available using the URL that appears below the site name field. In my case, it is https://gatsby-bugfender.netlify.app

Inspecting with Bugfender

You can inspect the logs from the Bugfender web console. As it starts collecting the logs, you can find them for each of your devices. In our case, it is a web application. Hence the device is the browser you have used to access the app.

Bugfender log details for a device

You can drill into and see the logs collected for a specific timeframe. In the image below, it shows the logs along with the user feedback created when a post is successfully published in our app.

Logs Drill-Down

It is also easy to spot the errors.

Errors

You can find issues, crashes, etc. under the respective tabs. In the screenshot below, we see an issue has been created as no article posts are found.

Issues

You can drill-down to the issue and send it to the GitHub for further triaging.

Send Issue to GitHub

Please explore the Bugfender app further for all the other options.

Before we go…

Bugfender is a tool that helps you finding errors in your production apps. We strongly believe in sharing knowledge and that’s why we create articles like this one. If you liked it, help us to continue creating content by sharing this article or signing up in Bugfender.

If you can't see our comments box below, your browser's tracking protection may be blocking Facebook Comments.

Trusted By

as_ford as_vorwerk GLS as_kohler as_taxify as_websummit Created with Sketch.

Already Trusted by Thousands

Bugfender is the best remote logger for mobile and web apps.

Get Started for Free, No Credit Card Required