Best Practices for Organizing Large Gatsby Projects with Multiple Plugins and Data Sources
Organize large Gatsby projects by maintaining the canonical /src and /plugins directory structure, grouping plugins logically in gatsby-config.js, leveraging themes for reusable configurations, and enabling performance features like parallel sourcing and stateful source nodes.
Managing a growing Gatsby codebase requires discipline as you integrate multiple CMSs, APIs, and databases. According to the gatsbyjs/gatsby source code and official documentation, strategic organization of your directory layout and plugin architecture prevents build slowdowns and reduces cognitive overhead for developers.
Maintain the Canonical Project Structure
Gatsby enforces a conventional file structure that separates auto-generated artifacts from source code. As documented in docs/docs/reference/gatsby-project-structure.md, you should treat /.cache and /public as build artifacts—both directories must be added to .gitignore and left untouched.
Place all application source code under /src, including components, pages, and templates. Reserve the top-level /plugins directory exclusively for local plugins (custom plugins that are specific to your project and version-controlled alongside your site). This separation ensures that your core source tree remains clean while reusable data-sourcing logic lives in isolated modules.
Group Plugins Logically in gatsby-config.js
The gatsby-config.js file serves as the central registry for all plugins. According to the Config API documentation in docs/docs/reference/config-files/gatsby-config.md, the order of the plugins array matters: Gatsby runs plugins in the sequence they are defined.
Structure your configuration by categorizing plugins into distinct groups:
- Source plugins (e.g.,
gatsby-source-wordpress,gatsby-source-contentful) that fetch external data - Transformer plugins (e.g.,
gatsby-transformer-sharp) that process raw data into Gatsby nodes - UI and SEO plugins (e.g.,
gatsby-plugin-image,gatsby-plugin-react-helmet)
Keep authentication tokens and endpoint URLs in environment-specific files (.env.development, .env.production) and reference them via process.env to keep credentials out of version control.
// gatsby-config.js
module.exports = {
siteMetadata: {
title: `My Large Site`,
description: `A Gatsby site with many data sources`,
},
plugins: [
// ── Source plugins ───────────────────────
{
resolve: `gatsby-source-wordpress`,
options: {
url: process.env.WP_GRAPHQL_ENDPOINT,
},
},
{
resolve: `gatsby-source-contentful`,
options: {
spaceId: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
},
},
{
resolve: `gatsby-source-stripe`,
options: {
objects: [`Price`, `Product`],
secretKey: process.env.STRIPE_SECRET_KEY,
},
},
// ── Transformer / UI plugins ──────────────
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-image`,
options: {
defaults: {
placeholder: `blurred`,
formats: [`auto`, `webp`],
},
},
},
`gatsby-plugin-react-helmet`,
`gatsby-plugin-sitemap`,
],
}
Encapsulate Complexity with Themes and Local Plugins
When multiple plugins always operate together (e.g., a blog setup requiring gatsby-source-contentful, image processing, and SEO utilities), bundle them into a Gatsby theme. Themes are essentially npm packages that export a gatsby-config.js, allowing you to share complex configurations across projects. The conceptual documentation in docs/docs/conceptual/plugins-themes-and-starters.md provides implementation patterns for creating composable themes.
For project-specific data sourcing that does not warrant a standalone npm package, use local plugins placed in the /plugins directory. This keeps custom sourcing logic version-controlled with your site while maintaining modularity.
A local plugin requires a standard package.json and a gatsby-node.js file:
my-gatsby-site/
└─ plugins/
└─ gatsby-plugin-my-cms/
├─ package.json
└─ src/
└─ gatsby-node.js
plugins/gatsby-plugin-my-cms/package.json:
{
"name": "gatsby-plugin-my-cms",
"main": "src/gatsby-node.js",
"license": "MIT"
}
plugins/gatsby-plugin-my-cms/src/gatsby-node.js:
exports.sourceNodes = async ({ actions, createNodeId, createContentDigest }, options) => {
const { createNode } = actions
const data = await fetch(options.apiUrl).then(r => r.json())
data.forEach(item => {
const node = {
id: createNodeId(`my-cms-${item.id}`),
parent: null,
children: [],
internal: {
type: `MyCmsItem`,
contentDigest: createContentDigest(item),
},
...item,
}
createNode(node)
})
}
Reference the local plugin in gatsby-config.js using require.resolve:
{
resolve: require.resolve(`./plugins/gatsby-plugin-my-cms`),
options: {
apiUrl: process.env.MY_CMS_ENDPOINT,
},
}
Optimize Build Performance with Parallel Sourcing
Starting with Gatsby 2.29, source plugins execute in parallel during the sourcing phase, dramatically reducing build times for sites with multiple network-bound data sources. No code changes are required to enable this behavior, but ensure your source plugins are compatible with parallel execution. This optimization is documented in docs/docs/reference/release-notes/v2.29/index.md.
For sites with high node churn (frequent additions and deletions), enable stateful source nodes (available in Gatsby 5.9+) to skip Gatsby’s expensive stale-node garbage collection. If your custom source plugin fully manages node lifecycles—including explicit deletion of removed nodes—invoke the enableStatefulSourceNodes action in the onPreInit lifecycle.
// gatsby-node.js in your source plugin or site's gatsby-node.js
exports.onPreInit = ({ actions }) => {
actions.enableStatefulSourceNodes()
}
This feature, detailed in docs/docs/reference/release-notes/v5.9/index.md, significantly reduces memory pressure on large sites by preventing Gatsby from diffing the entire node store against previous builds.
Separate Data Sourcing from Page Creation
Maintain strict separation between data fetching and UI rendering. Implement all data sourcing logic within sourceNodes (either in source plugins or local plugins), while keeping page-routing logic in the site's gatsby-node.js using the createPages API.
As documented in docs/docs/reference/config-files/gatsby-node.md, the createPages function should query the GraphQL layer after all plugins have finished sourcing, then map results to templates stored in /src/templates/. This decoupling prevents UI components from being entangled with data-fetching concerns.
A typical large project structure looks like this:
my-gatsby-site/
├─ .cache/ # auto-generated, ignored by Git
├─ public/ # auto-generated, ignored by Git
├─ src/
│ ├─ api/ # optional serverless functions
│ ├─ components/ # UI components
│ ├─ pages/ # file-system routing
│ ├─ templates/ # page templates for createPages
│ └─ gatsby-plugin-theme-ui/ # optional theme overrides
├─ plugins/ # local custom plugins
│ └─ gatsby-plugin-my-cms/
├─ gatsby-config.js # central plugin configuration
├─ gatsby-node.js # page creation logic
├─ gatsby-browser.js # browser APIs
├─ gatsby-ssr.js # server-side rendering APIs
└─ README.md # plugin inventory and setup instructions
Summary
- Preserve canonical directories: Keep
/.cacheand/publicgitignored, source code in/src, and custom plugins in/plugins. - Group plugins by function: Order source plugins before transformers in
gatsby-config.js, and externalize credentials to environment variables. - Use themes for reuse: Bundle related plugins into themes for cross-project sharing; use local plugins for single-project logic.
- Enable performance flags: Ensure you are on Gatsby 2.29+ for parallel sourcing, and use
enableStatefulSourceNodes(Gatsby 5.9+) for large, stateful data sources. - Decouple concerns: Handle data in
sourceNodesand routing increatePages, storing templates in/src/templates/.
Frequently Asked Questions
How should I structure the plugins directory for a large Gatsby site?
Create a top-level /plugins folder at the project root. Inside, create a subdirectory for each local plugin (e.g., /plugins/gatsby-source-custom-api) containing its own package.json and gatsby-node.js. Gatsby automatically recognizes these as resolvable plugins when you reference them with require.resolve('./plugins/gatsby-source-custom-api') in gatsby-config.js.
What is the difference between a Gatsby theme and a local plugin?
A theme is an npm package that encapsulates a reusable gatsby-config.js, plugins, and components, intended for distribution across multiple sites. A local plugin resides in your project's /plugins directory and is specific to that codebase. Use themes for shareable architectural patterns (like a company-wide blog setup) and local plugins for project-specific data sourcing that does not need external publication.
How can I speed up builds when using multiple source plugins?
First, upgrade to Gatsby 2.29 or later to leverage parallel sourcing, where Gatsby automatically runs sourceNodes for each plugin concurrently. Second, if your source plugin manages node deletions internally, enable stateful source nodes by calling actions.enableStatefulSourceNodes() in onPreInit (requires Gatsby 5.9+). This bypasses Gatsby's default node diffing algorithm, reducing memory overhead and build time for large datasets.
Where should I configure environment-specific variables for source plugins?
Define environment variables in .env.development and .env.production files, then reference them in gatsby-config.js via process.env.VARIABLE_NAME. This keeps sensitive tokens out of version control while keeping the configuration centralized in gatsby-config.js rather than scattered throughout your source code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →