Skip to content

Gatsby Template Components — Reusable Page Templates for Dynamic Pages

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Gatsby Template Components. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn Gatsby template components for programmatically created pages, including data queries, layout, and reusable patterns for dynamic content.

In this lesson, you'll understand how template components work with programmatic pages, how to structure them, and best practices for reusable templates.

What You'll Learn

How to create template components, query data with context variables, handle different content types, and build reusable layouts within templates.

Why It Matters

Template components determine how your dynamic pages look. Well-structured templates make it easy to add new content types and maintain consistent designs.

flowchart LR
    A[gatsby-node.js] --> B[createPage]
    B --> C[Template Component]
    C --> D[Page Query with $variables]
    D --> E[Data Fetched]
    E --> F[Component Renders]
    F --> G[HTML Output]
    style C fill:#639,color:#fff

Basic Template Structure

A template receives data and renders it:

// src/templates/blog-post.js
import { graphql, Link } from 'gatsby';
import React from 'react';
import Layout from '../components/Layout';

export default function BlogPost({ data }) {
  const post = data.markdownRemark;

  return (
    <Layout>
      <article>
        <header>
          <h1>{post.frontmatter.title}</h1>
          <p class="meta">
            {post.frontmatter.date} &middot; {post.timeToRead} min read
          </p>
        </header>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
        <footer>
          {post.frontmatter.tags?.map(tag => (
            <Link key={tag} to={`/tags/${tag}/`} class="tag">{tag}</Link>
          ))}
        </footer>
      </article>
    </Layout>
  );
}

export const query = graphql`
  query BlogPostBySlug($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      timeToRead
      frontmatter {
        title
        date(formatString: "MMMM D, YYYY")
        tags
      }
    }
  }
`;

Output: Each blog post page renders with the post title, date, reading time, HTML content, and tag links.

Multi-Content Template

Handle different content types in one template:

// src/templates/content-page.js
import { graphql } from 'gatsby';
import React from 'react';

export default function ContentPage({ data }) {
  // Works with both Markdown and Contentful content
  const content = data.markdownRemark || data.contentfulPage;

  const title = content.frontmatter?.title || content.title;
  const body = content.html || content.body?.childMarkdownRemark?.html;
  const image = content.frontmatter?.heroImage?.childImageSharp?.gatsbyImageData
    || content.heroImage?.gatsbyImageData;

  return (
    <div>
      <h1>{title}</h1>
      {image && <GatsbyImage image={getImage(image)} alt={title} />}
      <div dangerouslySetInnerHTML={{ __html: body }} />
    </div>
  );
}

export const query = graphql`
  query ContentPageBySlug($slug: String!) {
    # Try Markdown first
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter { title }
    }
    # Try Contentful as fallback
    contentfulPage(slug: { eq: $slug }) {
      title
      body { childMarkdownRemark { html } }
    }
  }
`;

Output: The same template renders pages from either Markdown or Contentful source, using whichever has matching data.

// src/templates/product.js
import { graphql } from 'gatsby';
import { GatsbyImage, getImage } from 'gatsby-plugin-image';
import React from 'react';

export default function ProductTemplate({ data }) {
  const product = data.productsYaml;

  return (
    <div>
      <h1>{product.name}</h1>
      <p class="price">${product.price}</p>
      <p>{product.description}</p>

      <h3>Features</h3>
      <ul>
        {product.features.map((feature, i) => (
          <li key={i}>{feature}</li>
        ))}
      </ul>

      <h3>Gallery</h3>
      <div class="gallery">
        {product.gallery?.map(image => (
          <GatsbyImage key={image.id}
            image={getImage(image.localFile.childImageSharp.gatsbyImageData)}
            alt={image.title || product.name} />
        ))}
      </div>
    </div>
  );
}

export const query = graphql`
  query ProductById($id: String!) {
    productsYaml(id: { eq: $id }) {
      name
      price
      description
      features
      gallery {
        title
        localFile {
          childImageSharp {
            gatsbyImageData(width: 400, height: 300)
          }
        }
      }
    }
  }
`;

Output: A product page with name, price, description, feature list, and an image gallery.

Reusable Template Layout

Extract common template layout into a wrapper:

// src/templates/PageTemplate.js
import { graphql } from 'gatsby';
import React from 'react';
import Layout from '../components/Layout';
import SEO from '../components/SEO';
import Sidebar from '../components/Sidebar';

export default function PageTemplate({ data, pageContext }) {
  const { content, seo } = extractContent(data);

  return (
    <Layout>
      <SEO title={seo.title} description={seo.description} />
      <div class="content-wrapper">
        <article class="main-content">
          <h1>{content.title}</h1>
          <div dangerouslySetInnerHTML={{ __html: content.bodyHtml }} />
        </article>
        <Sidebar type={pageContext.contentType} />
      </div>
    </Layout>
  );
}

function extractContent(data) {
  // Unified extraction for Markdown, Contentful, etc.
  const md = data.markdownRemark;
  const cf = data.contentfulPage;
  const wp = data.wpPage;

  if (md) return {
    content: { title: md.frontmatter.title, bodyHtml: md.html },
    seo: { title: md.frontmatter.title, description: md.excerpt }
  };
  if (cf) return {
    content: { title: cf.title, bodyHtml: cf.body?.childMarkdownRemark?.html },
    seo: { title: cf.seoTitle || cf.title, description: cf.seoDescription }
  };
  // ... WordPress handling
}

Output: The reusable template handles multiple content types through the extractContent helper, keeping the render logic clean.

Common Mistakes

  1. Not exporting the Graphql query: Templates require an exported query to fetch data. Without it, the component receives no data.
  2. Hardcoding context variable names: The variable name in the query ($slug) must match the key in createPage's context.
  3. Forgetting to import Layout: Templates should use a layout wrapper for consistent structure. Each template should not redefine navigation and footer.
  4. Overly specific templates: Create one reusable template per content type rather than duplicating code. Use variations through page context.
  5. Not handling missing data: Content may have optional fields. Use optional chaining (?.) and fallbacks (|| 'Untitled') to prevent crashes.

Practice Questions

  1. Where do template components live? Answer: In src/templates/. They're referenced by gatsby-<a href="/backend/nodejs/">Node.js</a> when creating pages programmatically.

  2. How does a template know which data to fetch? Answer: Through context variables passed in createPage. The template's page query uses these variables as $slug, $id, etc.

  3. Can one template handle multiple content types? Answer: Yes. Query all possible types and use conditional logic to pick the matching one in the component.

  4. What is the purpose of the Layout component in a template? Answer: It provides consistent header, footer, navigation, and styling across all pages created from the template.

Challenge

Create a template that handles three content types: BlogPost, Tutorial, and CaseStudy. Each type has different fields but shares a common layout. Use page context to differentiate them.

Mini Project

Build a news site with three template types: Article (standard news), Feature (long-form with hero image), and Opinion (with author bio sidebar). All templates share a common Layout with navigation and footer.

FAQ

Can I use CSS Modules in template components?

: Yes. Import .module.css files in templates the same way as any other component.

Do templates support client-side state?

: Yes. Templates are regular React components. Use hooks, context, or signals for client-side state.

How do I add pagination to a template?

: Pass currentPage, totalPages, hasNextPage, and hasPreviousPage through page context.

Can templates use `useStaticQuery`?

: Yes. Templates can use both page queries and useStaticQuery for additional data.

What's Next

Learn about Building a Blog from Markdown to create a complete blog with posts, tags, and archives.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro