Resources
What & Link | Type |
---|---|
Gatsby Docs | Documentation |
Master example repo | Examples |
Environment Variable
Running gatsby develop
sets process.env.NODE_ENV = 'development'
. More info here
Troubleshooting Gatsby
When in doubt, rebuild node_modules
rm -rf node_modules && npm install
Optional: delete package-lock.json
Misc issues:
- Plugin can't be resolved
- Make sure the name is typed correctly
- If developing a local plugin, make sure that you put it in
{root}/plugins/{myPlugin}
and not{root}/src/plugins/{myPlugin}
. Plugins dir needs to be in same directory aspackage.json
- Updates not showing
- Try using
gatsby clean
- Try using
- Error on trying to debug with:
basedir= ... missing ) after argument list
on Windows- Make sure the path you are calling looks like
node_modules/gatsby/dist/bin/gatsby
and notnode_modules/.bin/gatsby
- Docs were updated in this commit
- Make sure the path you are calling looks like
- Error:
Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "___" does not
- This is an error with the GraphQL side of things. Basically, for GraphQL there is a rule for what kind of string can make up a field / key, and that RegEx is used to validate.
- For example,
""
(empty) and "0_Hello" (numerical as leading char) are both illegal and will throw this error
- For example,
- This was really hard for me to personally track down:
- I was putting passing a very large JSON object through to my page templates by using
actions.createPage({context: myLargeJsonObj})
- The issue was that Gatsby / GraphQL was actually expanding the object, as-in it was making all the nested sub-fields of my object query-able in GraphQL (which I did not want), and thus making them all GraphQL fields.
- Somewhere in my object I must have had a field that violated this regex
- A simple solution in my situation was to simply use
context: JSON.stringify(myLargeJsonObj)
- I was putting passing a very large JSON object through to my page templates by using
- This is an error with the GraphQL side of things. Basically, for GraphQL there is a rule for what kind of string can make up a field / key, and that RegEx is used to validate.
Working with Nodes
Manually adding / creating Nodes for GraphQL
Important Docs
Basic How-To
A good case for this is when you have data that you want to make available inside every template component, but without manually passing. You could do this by attaching the data to existing nodes (e.g. MarkdownRemark nodes) via context
, but if the data you want to pass is shared between all nodes, that is unnecessary bloat, and a better solution is to create a node.
The basic step is to use createNode
inside the onCreateNode
callback.
Example - gatsby-node.js
:
exports.sourceNodes = ({ actions, createContentDigest, createNodeId }) => {
const myThing = {
name: 'Joshua',
drinksCoffee: true
};
const hash = createContentDigest(myThing);
createNode({
id: createNodeId(`mything-${hash}`),
children: [],
parent: null,
...myThing,
internal: {
content: JSON.stringify(myThing),
contentDigest: hash,
type: 'MyCustomSource',
},
});
}
This post, "how to write a Gatsby source plugin featuring cat facts" is also a good intro.
If you only have one "thing" to pass around, you could also just add it to the
site
orsiteMetaData
Node
WARNING: If you use createNode outside of
exports.sourceNodes
, you might find that your created node disappears! This is because currently creating nodes is only supported in that callback (or in a plugin) - see these links for clarification - a, b, c.
See below section for details.
Issues with the order of node creation
If you didn't see my warning earlier, you might have noticed that if you try to use createNode
outside of the sourceNodes
callback, the node that you created usually will disappear; creating them outside of sourceNodes
is not currently supported due to the specific order of node creation.
Honestly, this is one of the things about Gatsby that infuriates me the most; there are a bunch of idiosyncrasies that are not very well documented and make manual node creation overly complicated. For example:
- No node creation outside
sourceNodes
callback - No using
graphql
queries insidesourceNodes
callback- You can use
getNodesByType()
as a poor substitute
- You can use
- It seems like
createPages
gets called aftersourceNodes
, so you can't reliably create new nodes aftercreatePages
is called- If you try to add new nodes after the createPages callback (or in it), it looks like there is another wipe and they get removed
- If you try to get around this by making
sourceNodes
async, and awaiting a resolver from createPages, it will just hang onsource and transform nodes
, since it waits for sourceNodes to finish before moving on to create pages
- Gatsby tries to run
sourceNodes
parallel (async) withonCreateNode
- This really makes things difficult, because if you are using existing nodes as source material to create new ones, you can't guarantee that all the nodes will be there when
sourceNodes
gets called- For example, if you are creating a
Playlist
node based on MarkdownRemark nodes and frontmatter, you might find that usinggetNodesByType('MarkdownRemark');
gives you less nodes than it should be - This will probably also be random, since they are basically working in a race condition against each other
- You can pretty easily verify this for yourself, by doing something like:
export.sourceNodes = ({getNodesByType}) => { const fileNodes = getNodesByType('File'); const mdFiles = fileNodes.filter(file => file.ext === '.md'); const mdNodes = getNodesByType('MarkdownRemark'); console.log(`Expected mdNodes count = ${mdFiles.length}. Actual = ${mdNodes.length}`); }; // In my case, these were off - expected was always higher than the actual nodes from Gatsby internals
- For example, if you are creating a
- This is probably the worst out of all the issues. I understand why this is the case - since sourceNodes can create new nodes, and onCreateNode has to be called for every node, it doesn't make sense to force it to run last. However, it still seems like there should be an extra hook or callback somewhere that basically lets you receive all the nodes, then push new ones that you don't care about getting "observed" by
onCreateNode
.
- This really makes things difficult, because if you are using existing nodes as source material to create new ones, you can't guarantee that all the nodes will be there when
Workarounds
By far, the easiest solution for if you want to add data based on existing nodes is to simply attach the data directly to the nodes, in the onCreateNode
callback, with actions.createNodeField()
.
If you really need to create brand new nodes based on existing nodes, there is one workaround that I was able to find. I'm very thankful for this comment I found on a random thread, that has a pretty smart workaround.
Here is the basic run down:
- In
sourceNodes
, you immediately figure out how many nodes you are expecting (and need) before you want to actually start creating nodes- Save this expected total as a file scoped variable, so it can be checked in
onCreateNode
- Save this expected total as a file scoped variable, so it can be checked in
- You return a promise from
sourceNodes
, and you expose the resolver of that promise as a file scoped variable- This means can call the resolver from within
exports.onCreateNode
- This is effectively pausing
sourceNodes
until you call the resolver.
- This means can call the resolver from within
- Keep counters of nodes as they are processed through
onCreateNodes
.- Once the counter reaches the expected value you recorder earlier, call the promise resolver, which will call whatever code you have defined as the promise callback
- This ensures that the right number of nodes have been processed before your code executes
My slight modification to this was to also store some nodes, as I processed them in onCreateNode
, in the file / module scope. This way, when my promise was called, I could access those full nodes from sourceNodes
, which helped since some of the data I could only get with a GraphQL query, which you can't use in sourceNodes
.
Warning: This method will probably be slower than using
sourceNodes
as normal, since you are not allowing it to run parallel toonCreateNode
.
Plugin development
Remark Based Plugin
- Main Tutorial: Remark Plugin Tutorial
- Existing plugins to look at source code of:
- gatsby-remark-component
- Changes the AST node parent of a custom component to a div
- gatsby-remark-prismjs
- gatsby-remark-responsive-iframe
- DocSPA short-codes
- gatsby-remark-component
- Other helpful resources:
- AST Explorer - Markdown
- Daniel Worsnup - How to Build a Simple Markdown Plugin for your Gatsby Site
- Really good coverage of "the basics"
- huy.dev - multi-part tutorial: Writing plugins for gatsby-transformer-remark (1, 2, 3)
- Part 2 is especially helpful
- Adrian Perez: Creating a Gatsby Plugin for Awesome Links
- Greg Perlman: Creating a Gatsby Plugin with TypeScript
- https://using-remark.gatsbyjs.org/
- Jason Lengstorf: How to Modify Nodes in an Abstract Syntax Tree
- Swizec Teller: Moving 13 years of WordPress blog to Gatsby Markdown
- TypeScript types
Helpful Packages
Name | Package | Description |
---|---|---|
syntax-tree/unis-util-visit | unist-util-visit |
Utility to visit AST nodes |
syntax-tree/unis-util-select | unist-util-select |
Very neat - utility that lets you select nodes with a syntax close to CSS queryselectors |
Existing Plugins
Name | Description |
---|---|
gatsby-* - folder | These are the default Gatsby plugins, hosted with the Gatsby mono-repo. Great examples. |
gatsby-remark-component | Changes the parent node / element of a custom component to a div |