This episode covers architecture and design patterns for Gatsby: a scalable project structure, component-driven design and content modeling, patterns for shared components, hooks, and utilities, and strategies for maintainability and collaboration.

As a site grows, good architecture becomes more valuable than new features. Decisions about folder structure, how to split components, and how to share code determine whether a project stays easy for new team members to understand.
Episode 18 covers a scalable Gatsby project structure, component-driven design and content modeling, patterns for shared components, hooks, and utilities, and strategies for maintainability and collaboration.
The src folder should be grouped by function rather than merely by file type. The following structure separates pages, templates, components, and hooks with clear responsibilities:
src/
pages/
templates/
components/
layout/
ui/
hooks/
utils/
data/
styles/src/pages holds file-based routing, src/templates holds templates for pages created programmatically via createPage, while src/components holds reusable components.
For large projects, the components folder can be split further per feature — for example, components/blog/ and components/checkout/. Feature-based separation keeps code that changes together together, so teams working on different features rarely collide on the same files. This structure also makes code review easier: changes in one feature are clearly visible without touching another feature's folders.
Put files that change together side by side: a component's styles, tests, and hooks live in that component's folder. Colocation shortens the search distance and makes each component a self-contained unit. When a component is deleted, all its dependents go with it without leaving floating artifacts. A simple rule: if two files always change together, keep them adjacent.
Design components from small pieces, then assemble them into larger ones. Pages are built from sections, sections from cards, and cards from presentational elements. This consistency lets a design change be made at just one level. An overly large component signals a missing composition layer — break it into smaller parts with single responsibilities.
The content types in your data model should match what the pages need. When defining the GraphQL schema in Gatsby, pull commonly used fields up to the same level so template queries stay simple and don't duplicate transformation logic. A good content model is a predictable one: anyone reading the schema immediately understands what data is available without guessing. Changing a model in the CMS costs more than changing a component, so think carefully before adding a new field. Start with the smallest model that satisfies the pages, then grow it when the need genuinely appears.
Logic used by many components — reading site metadata, accessing query data — moves into a custom hook. Here's an example of reading site metadata:
import { useStaticQuery, graphql } from "gatsby"
export const useSiteMetadata = () => {
const data = useStaticQuery(graphql`
query SiteMetadata {
site {
siteMetadata {
title
description
}
}
}
`)
return data.site.siteMetadata
}useSiteMetadata wraps the useStaticQuery query so consumer components just call one hook instead of writing the query repeatedly. The use naming convention is mandatory so React recognizes the hook and applies the rules of hooks correctly.
Hooks can be tested like plain functions — wrap them in a small component and assert the result. This pattern keeps React's rules satisfied while ensuring the logic stays correct. Put hook tests in the same folder as the hook so they stay colocated and easy to find.
Data transformation functions — date formatting, slug generation, filtering — are separated into src/utils as pure functions. Pure functions are easy to test because they take input and return output without side effects. If a function starts reading localStorage or writing to the DOM, move that responsibility into a hook or component. Use descriptive function names so their usage is predictable without reading the implementation.
Establish naming conventions for components, event handlers, and CSS before the team starts writing code. Using TypeScript adds a safety layer: misspelled props get caught at compile time rather than runtime. Document architecture decisions in a short README so new team members don't have to guess.
Also agree on team tooling — formatter, linter, and test runner — and commit the configuration to the repository. That way every team member gets the same behavior without manual setup. Agreed configuration also reduces noise in pull requests that should focus on the logic of the change.
Include a README explaining how to run the project, the folder structure, and the conventions in force. Good documentation speeds up onboarding for new team members and reduces repeated Q&A. Update documentation in the pull requests that change the workflow, rather than putting it off until the end of the sprint. Keep documentation short and focused on what's frequently asked.
Keep a habit of small refactors whenever you add a feature, rather than waiting for technical debt to pile up. Small, focused pull requests are easier to review and produce fewer conflicts than giant changes. Make refactoring part of the definition of done, not a separate task that always gets postponed.
Key takeaways:
src folders by function and domain.src/utils are easy to test.In the next episode, episode 19, we'll discuss modern tooling and build automation — the Gatsby CLI, TypeScript support, CI/CD pipelines, and linting, formatting, and pre-commit hooks.