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 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/.
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:
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 likelocalstorage
, and objects likewindow
work well.gatsby build
withgatsby 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 onnodejs
. For example, thewindow
object is not available in thenodejs
and we may end up getting an error like:
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 theAPI_KEY
you noted down while setting upBugfender
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 intoFile
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 aMarkdownRemark
node so that we can query the HTML representation of the markdown. We will use thegatsby-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:
- 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
]
- Change the value of the
title
property of thesiteMetadata
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.
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.
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:
- 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
. - 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.
- Create pages for each of the nodes. Here, we are telling the Gatsby build process to use the
blog-post.js
file under thesrc/templates
folder to create pages. These pages will be used when our users click on the article title to get to article details. - Next, we loop through the tags of all the articles and create a set (which is the unique collection in JavaScript) of unique tags.
- Create a page for each of the tags. Here, we are telling the Gatsby build process to use the
tagged-post.js
file under thesrc/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.
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.
In this article, we have chosen Netlify, but there are other services you can use to deploy your app. You can find an exhaustive comparison here:
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.
In the next step, provide the build settings as shown in the image below.
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.
Netlify creates a site for you with a random name. However, you can change it as per your choice based on availability.
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.
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.
It is also easy to spot the 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.
You can drill-down to the issue and send it to the GitHub for further triaging.
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.
Other articles you might find interesting: