Joshua's Docs - Gatsby Js

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 as package.json
  • Updates not showing
    • Try using gatsby clean
  • 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 not node_modules/.bin/gatsby
  • 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
    • 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)

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 or siteMetaData 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 inside sourceNodes callback
    • You can use getNodesByType() as a poor substitute
  • It seems like createPages gets called after sourceNodes, so you can't reliably create new nodes after createPages 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 on source and transform nodes, since it waits for sourceNodes to finish before moving on to create pages
  • Gatsby tries to run sourceNodes parallel (async) with 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 sourceNodes gets called
      • For example, if you are creating a Playlist node based on MarkdownRemark nodes and frontmatter, you might find that using getNodesByType('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
    • 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.

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
  • 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.
  • 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 to onCreateNode.


Plugin development

Remark Based Plugin

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
Markdown Source Last Updated:
Wed Mar 11 2020 17:28:22 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Tue Dec 10 2019 03:23:51 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback