Katie
Posted on June 18, 2020
I discovered that a directory only needs two small files in it for the Gatsby static website generator to turn it into a functioning index.html
with a body of <div>Hello world!</div>
.
My next project is to build it up from an index.md
Markdown file containing the message of "Hello world!
" and have Gatsby inject it into index.html
.
I'm still just shooting for some variation on this HTML for the contents of index.html
:
<html>
<head></head>
<body>
<div>Hello World</div>
</body>
</html>
index.md
looks different than I'm used to
In this case, I'm going to use an index.md
file that doesn't have a Markdown-formatted "body;" it just has YAML-formatted "front matter" serving as data, like this:
---
message: Hello World
---
Again, note that I put "Hello World
" in the "front matter" up top, not down in the body as Markdown like this:
---
---
Hello World
Jamstack Conf takeaways and Hopin thoughts
Katie ・ Jun 8 ・ 9 min read
Since Topher Zimmerman's training on behalf of Magnolia CMS at Jamstack Conf 2 weeks ago, while further exploring Stackbit and TinaCMS, I've been learning that it's pretty common for Markdown files being used as data for static site generation with highly "WYSIWYG" drag-and-drop content management systems (CMSes) to have most data stored in the front matter of the .md
file, not the body.
This lets you take advantage of YAML's ability to store "nested, ordered data" nicely.
That property CMSes help authors specify, with drag-and-drop interactivity, intent such as:
"Page sub-component B2 goes inside of page component B, after page sub-component B1, which is also inside of thing B.
"Page component B goes between page component A and page component C."
In fact, even if I wanted to let an author use Markdown to format "Hello World
" into <ul><li>Hello</li><li>World</li></ul>
, it's not unheard of to store that in the front matter too, like this:
---
message: |-
* Hello
* World
---
Instead of in the "body" of the .md
file like this:
---
---
* Hello
* World
In Gatsby, it's traditional to put such an index.md
file in a folder called pages
that in turn is in a folder called src
.
Furthermore, I have to include some "front matter" to specify what JavaScript "template" file I'm going to use to wrap the words "Hello World
" between <div>
& </div>
tags, and it's conventional to simply call that front-matter property "template
." I'm planning on naming the template "xyzzy
," so I'll add template: xyzzy
to the front matter.
Files
/src/pages/index.md
In short, my index.md
file will have the following contents:
---
template: xyzzy
message: Hello World
---
/package.json
As in the 2-file Gatsby minimum viable build, I have to specify that certain Node packages like React and like Gatsby itself are essential to building the static site. I'll also specify two Gatsby plugins called gatsby-source-filesystem
and gatsby-transformer-remark
that I'll need:
{
"name" : "netlify-gatsby-test-02",
"description" : "Does this really work?",
"version" : "0.0.2",
"scripts" : {
"develop": "gatsby develop",
"start": "npm run develop",
"build": "gatsby build",
"serve": "gatsby serve"
},
"dependencies" : {
"gatsby": ">=2.22.15",
"gatsby-source-filesystem": ">=2.3.11",
"gatsby-transformer-remark": ">=2.8.15",
"react": ">=16.12.0",
"react-dom": ">=16.12.0"
}
}
/gatsby-config.js
I need to "activate" the Gatsby plugins gatsby-source-filesystem
and gatsby-transformer-remark
with a file called gatsby-config.js
:
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages`,
},
},
`gatsby-transformer-remark`
]
};
/src/templates/xyzzy.js
Gatsby leans heavily upon a JavaScript library called React, and specifically upon its notion of "React components" (not to be confused with the contents of /src/components/
in this project's folder structure, although that most certainly is a folder full of files defining React components -- it's just that /src/templates
is also full of React components).
- The best way to learn what I mean by "React component" is to read the first 4 sections of the official React documentation. They're well-written in tutorial style, so don't worry -- it's not a dry read.
Whenever Gatsby is told to use a given React component such as xyzzy.js
to render a given "page" of a web site that it's decided needs rendering (I'll soon write code in a file called gastby-node.js
telling xyzzy.js
about index.md
), it passes information into the Xyzzy
component as pageContext
.
- If you define your React component as a function, name its parameter
pageContext
. - If you defined your React component as a class that extends
React.Component
, you'll automatically have this information accessible within your class asthis.props.pageContext
.
I'll dynamically fetch the text "Hello World
" from /src/pages/index.md
as a detail of pageContext
called pageContext.frontmatter.message
(or, if I had been using classes, this.props.pageContext.frontmatter.message
).
Note that the "frontmatter
" property of pageContext
doesn't exist yet. I'll soon write code in a file called gastby-node.js
that makes Gatsby include frontmatter
in pageContext
.
For now, just trust that {pageContext.frontmatter.message}
is the equivalent of Hello World
because that's what's in the "front matter" of index.md
.
Variation 1 (standalone template)
import React from "react"
export default function Xyzzy({ pageContext }) {
return (
<div>
{pageContext.frontmatter.message}
</div>
)
}
If I don't have anything too complicated to put into the home page of my web site, I can write DIV
tags directly into xyzzy.js
surrounding {pageContext.frontmatter.message}
.
Note that <div>{pageContext.frontmatter.message}</div>
technically isn't HTML -- it's JSX / a React element.
- If you're not sure what I mean by that, read the first 4 sections of the official React documentation. It won't take you long.
Variation 2 (leveraging another component)
import React from "react"
import BasicDiv from '../components/basicDiv.js';
export default function Xyzzy({ pageContext }) {
return (
<BasicDiv messageToDisplay={pageContext.frontmatter.message} />
)
}
If I'm feeling fancy and prefer to delegate the building of the JSX React element <div>Hello World</div>
to a different React Component (for code modularity and reusability), I can add an extra file /src/components/basicDiv.js
to my directory structure, "import" it into xyzzy.js
, and execute it from within xyzzy.js
's JSX as .
To pass BasicDiv
a parameter specifying "Hello World
" as seen in index.md
, I'll make that information an attribute of the call to <BasicDiv />
.
/src/components/basicDiv.js
Note that I only need this file when using "variation 2" of /src/templates/xyzzy.js
.
import React from "react"
export default function BasicDiv(props) {
return (
<div>
{props.messageToDisplay}
</div>
)
}
The BasicDiv
React component is never used directly by Gatsby in the building of a "page" from data such as the contents of index.md
, so when defining it as a function, its parameter name should be a more traditional props
rather than pageContext
.
Because I know I summoned it elsewhere with a message
parameter formatted <BasicDiv message={some-data-here} />
, I can access the contents of message
as props.message
(or, if I'd defined BasicDiv
as a class, as this.props.message
).
Since BasicDiv
is never passed any sort of parameter named pageContext
by Gatsby, the HTML-like JSX code I use to render my DIV
is, instead, <div>{props.message}</div>
or <div>{this.props.message}</div>
.
/gatsby-node.js
Finally, I come to the beastly file that teaches Gatsby how to put everything together: gatsby-node.js
.
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const urlSuffixIdea = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `suggestedURLSuffix`,
value: urlSuffixIdea,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const queryResult = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
suggestedURLSuffix
},
frontmatter {
template,
message
}
}
}
}
}
`)
nodes = queryResult.data.allMarkdownRemark.edges
nodes.forEach(({ node }) => {
createPage({
path: node.fields.suggestedURLSuffix,
component: path.resolve(`./src/templates/${node.frontmatter.template}.js`),
context: {
frontmatter: node.frontmatter,
},
})
})
};
Overriding createPages
Earlier, I said that I would need to teach Gatsby to pass details from the "front matter" of index.md
to xyzzy.js
as a frontmatter
sub-property of a Gatsby concept called pageContext
.
I do this by overriding Gatsby's definition of a Gatsby function called
createPages()
within a file calledgatsby-node.js
that I'll place in the root directory of my folder structure.
The first thing I do is tell Gatsby to make a GraphQL query against its entire self-inventory of stuff it knows about and likes to call "nodes" (not to be confused with Node for which Gatsby is a package), filtering to only return the ones that seem to be Markdown-formatted files, and save the resulting JavaScript object into a variable I decided to call queryResult
.
The part of queryResult
that I'm interested in is an array of "node
" objects accessible through queryResult.data.allMarkdownRemark.edges
-- I'll set that array aside into a variabled called nodes
.
(In my case, there's only one object in the nodes
array: the one representing the contents of the file /src/pages/index.md
.)
For each loop over a node
in nodes
, I'll call a Gatsby function actions.createPage()
.
- I'll check what the
node
'ssuggestedURLSuffix
is and tell Gatsby to make the web page it renders available athttps://mysite.com/whatever-is-in-that-url-suffix
by settingpath
in the JavaScript object I passcreatePage()
tonode.fields.suggestedURLSuffix
.- More in a minute on where the value of
suggestedURLSuffix
comes from.
- More in a minute on where the value of
- I'll also look through the front-matter properties of my Markdown-formatted file for one called
template
and concatenate it with other data to indicate a file path in my folder structure where Gatsby can find the definition of the React component that I'd like to use for transforming the Markdown-formatted file into HTML on my actual web site. In the case ofindex.md
, there's a property oftemplate: xyzzy
, and so settingcomponent
topath.resolve(`./src/templates/$xyzzy.js`)
will ensure thatindex.md
is associated withxyzzy.js
. - Finally, I will say that I'd like the
node.frontmatter
details returned by my GraphQL query to be passed along toxyzzy.js
as part ofpageContext
by including them in thecontext
property of the JavaScript object I passcreatePage()
.context
itself takes a JavaScript object as a value, into which I put just one key-value pair:frontmatter
as the key, andnode.frontmatter
as the value.
Overriding onCreateNode
I promised to explain the origin of suggestedURLSuffix
.
I define suggestedURLSuffix
by overriding Gatsby's definition of a Gatsby function called onCreateNode
, also within the gatsby-node.js
file.
Basically, I can use
onCreateNode()
to trick GraphQL into thinking that the "nodes" it's querying have properties they don't inherently have -- likesuggestedURLSuffix
.
In my case, I populate the value of suggestedURLSuffix
with output from a call to the gatsby-source-filesystem
plugin's createFilePath()
function.
Output
That's it! Just 5 files (or 6, to demonstrate breaking a BasicDiv
function out of Xyzzy
) and you're up and running with Markdown in Gatsby.
The resulting page has the following HTML:
<!DOCTYPE html>
<html>
<head>
<meta charSet="utf-8"/>
<meta http-equiv="x-ua-compatible" content="ie=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta name="generator" content="Gatsby 2.23.3"/>
<link as="script" rel="preload" href="/component---src-templates-xyzzy-js-61a4d70344455ce4bf22.js"/>
<link as="script" rel="preload" href="/framework-4d07bacc3808af3f4337.js"/>
<link as="script" rel="preload" href="/app-3cfcd55108b187700e99.js"/>
<link as="script" rel="preload" href="/webpack-runtime-2f540f584be2422b9aa4.js"/>
<link as="fetch" rel="preload" href="/page-data/index/page-data.json" crossorigin="anonymous"/>
<link as="fetch" rel="preload" href="/page-data/app-data.json" crossorigin="anonymous"/>
</head>
<body>
<div id="___gatsby">
<div style="outline:none" tabindex="-1" id="gatsby-focus-wrapper">
<div>Hello World</div>
</div>
<div id="gatsby-announcer" style="position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0" aria-live="assertive" aria-atomic="true"></div>
</div>
<script id="gatsby-script-loader">/*<![CDATA[*/window.pagePath="/";/*]]>*/</script><script id="gatsby-chunk-mapping">/*<![CDATA[*/window.___chunkMapping={"app":["/app-3cfcd55108b187700e99.js"],"component---src-templates-xyzzy-js":["/component---src-templates-xyzzy-js-61a4d70344455ce4bf22.js"]};/*]]>*/</script><script src="/webpack-runtime-2f540f584be2422b9aa4.js" async=""></script><script src="/app-3cfcd55108b187700e99.js" async=""></script><script src="/framework-4d07bacc3808af3f4337.js" async=""></script><script src="/component---src-templates-xyzzy-js-61a4d70344455ce4bf22.js" async=""></script>
</body>
</html>
Helpful links
- This codebase on GitHub, as well as its "BasicDiv" variant
- React documentation (the first four pages are solid gold)
- Three tips about ES6 JavaScript syntax in case you haven't worked with it
- The JavaScript Language - Export and Import
- A dead simple intro to destructuring JavaScript objects
- Gatsby official tutorial: Get to Know Gatsby Building Blocks
- Gatsby official tutorial: Programmatically create pages from data
- Gotcha:
gatsby-transfomer-remark
must be activated in gatsby-config.js
Posted on June 18, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.