New Launch

Take a deep dive into the fastest Gatsby, yet: Gatsby 5!

Node Creation

Nodes are created by calling the createNode action. Nodes can be any object.

A node is stored in Redux under the nodes namespace, whose state is a map of the node ID to the actual node object.

Sourcing Nodes

The creation of nodes occurs primarily in the sourceNodes bootstrap phase. Nodes created during this phase are top level nodes. I.e, they have no parent. This is represented by source plugins setting the node’s parent field to null. Nodes created via transform plugins (who implement onCreateNode) will have source nodes as their parents, or other transformed nodes. For a rough overview of what happens when source nodes run, see the traceId illustration.

Parent/Child/Refs

There are a few different scenarios for creating parent/child relationships.

Node relationship storage model

All nodes in Gatsby are stored in a flat structure in the Redux nodes namespace. A node’s children field is an array of node IDS, whose nodes are also at the top level of the Redux namespace. Here’s an example of the nodes namespace.

An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they’re all packed into. This has some implications on child field inference in the Schema Generation phase.

Explicitly recording a parent/child relationship

This occurs when a transformer plugin implements onCreateNode in order to create some child of the originally created node. In this case, the transformer plugin will call createParentChildLink, with the original node, and the newly created node. All this does is push the child’s node ID onto the parent’s children collection and resave the parent to Redux.

This does not automatically create a parent field on the child node. If a plugin author wishes to allow child nodes to navigate to their parents in GraphQL queries, they must explicitly set childNode.parent: 'parent.id' when creating the child node.

Foreign Key reference (___NODE)

We’ve established that child nodes are stored at the top level in Redux, and are referenced via ids in their parent’s children collection. The same mechanism drives foreign key relationships. Foreign key fields have a ___NODE suffix on the field name. At query time, Gatsby will take the field’s value as an ID, and search Redux for a matching node. This is explained in more detail in schema gqlTypes.

Plain objects at creation time

Let’s say you create the following node by passing it to createNode

The value for baz is itself an object. That value’s parent is the top level object. In this case, Gatsby saves the top level node as is to Redux. It doesn’t attempt to extract baz into its own node. It does however track the subobject’s root NodeID using Node Tracking

During schema compilation, Gatsby will infer the sub object’s type while creating the gqlType.

Fresh/stale nodes

Every time a build is re-run, there is a chance that a node that exists in the Redux store no longer exists in the original data source. E.g. a file might be deleted from disk between runs. We need a way to indicate that fact to Gatsby.

To track this, there is a Redux nodesTouched namespace that tracks whether a particular node ID has been touched. This occurs whenever a node is created (handled by CREATE_NODE), or an explicit call to touchNode.

When a source-nodes plugin runs again, it generally recreates nodes (which automatically touches them too). But in some cases, such as transformer-screenshot, a node might not change, but we still want to keep it around for the build. In these cases, we must explicitly call touchNode.

Any nodes that aren’t touched by the end of the source-nodes phase, are deleted. This is performed via a diff between the nodesTouched and nodes Redux namespaces, in source-nodes.ts

Changing a node’s fields

From a site developer’s point of view, nodes are immutable. In the sense that if you change a node object, those changes will not be seen by other parts of Gatsby. To make a change to a node, it must be persisted to Redux via an action.

So, how do you add a field to an existing node? E.g. perhaps in onCreateNode, you want to add a transformer specific field? You can call createNodeField and this will add your field to the node’s node.fields object and then persists it to Redux. This can then be referenced by other parts of your plugin at later stages of the build.

Node Tracking

When a node is created, createNode will track all its fields against its nodeId. See Node Tracking Docs for more.

Edit this page on GitHub
© 2022 Gatsby, Inc.