Gatsby Template Components — Reusable Page Templates for Dynamic Pages
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} · {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.
Template with Image Gallery
// 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
- Not exporting the Graphql query: Templates require an exported
queryto fetch data. Without it, the component receives no data. - Hardcoding context variable names: The variable name in the query (
$slug) must match the key increatePage'scontext. - Forgetting to import Layout: Templates should use a layout wrapper for consistent structure. Each template should not redefine navigation and footer.
- Overly specific templates: Create one reusable template per content type rather than duplicating code. Use variations through page context.
- Not handling missing data: Content may have optional fields. Use optional chaining (
?.) and fallbacks (|| 'Untitled') to prevent crashes.
Practice Questions
Where do template components live? Answer: In
src/templates/. They're referenced bygatsby-<a href="/backend/nodejs/">Node.js</a>when creating pages programmatically.How does a template know which data to fetch? Answer: Through
contextvariables passed increatePage. The template's page query uses these variables as$slug,$id, etc.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.
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
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