Every Gatsby fix we catalog

SEOLZ catalogs 26 fixes for Gatsby across 2 areas — SEO, answer-engine readiness, accessibility, security and site-health. Each lists the exact steps for Gatsby, with a link to the official docs.

SEO · 23 fixes

Duplicate meta descriptionModerate effort

Write a unique, page-specific meta description for every page on your store so Google can display a compelling, accurate snippet for each one in search results.

On Gatsby

  1. Create a reusable `<Seo>` component at `src/components/Seo.jsx` that uses Gatsby's built-in Head API (Gatsby ≥ 4.19) — export a named `Head` function from every page file instead of using react-helmet, so metadata is rendered server-side.
  2. In every page file (e.g. `src/pages/index.jsx`, `src/pages/products/[handle].jsx`, `src/templates/product.jsx`, `src/templates/collection.jsx`), export a named `Head` function that calls `<Seo>` with a unique `description` prop derived from that page's own data (product description, collection summary, CMS copy, etc.).
  3. For programmatic pages created via `gatsby-node.js` / `createPage`, pass the page-specific description through `pageContext` so the template's `Head` export can consume it without an extra GraphQL query.
  4. Guard against empty or truncated descriptions: strip HTML tags, collapse whitespace, and clamp to 155 characters inside the `<Seo>` component so every rendered tag is clean and within Google's preferred length.
  5. Run `gatsby build && gatsby serve` and inspect the rendered `<head>` with DevTools or `curl` to confirm every route emits a distinct, non-empty `<meta name='description'>` — no two pages should share the same string.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <meta name="description" content="Shop women's running shoes at Acme Store — lightweight, cushioned designs for road and trail. Free shipping on orders over $50.">
  <title>Women's Running Shoes | Acme Store</title>
</head>

Place inside the <head> element of every page. Replace the content value with a unique 120–160 character description for that specific page.

Duplicate titleModerate effort

Give every page a unique, descriptive title tag that accurately reflects that page's specific content.

On Gatsby

  1. Create (or update) a reusable `<Seo>` component at `src/components/Seo.jsx` that uses Gatsby's built-in Head API (`export function Head`) — no third-party library needed in Gatsby v4+.
  2. In every page file under `src/pages/` (e.g. `index.jsx`, `products/[handle].jsx`, `collections/[slug].jsx`, `about.jsx`) export a named `Head` function that renders the `<Seo>` component with a page-specific `title` prop.
  3. For template-driven pages (product detail, collection, blog post) created via `gatsby-node.js` / `createPage`, pass the unique title through `pageContext` so the template's `Head` export can consume it dynamically.
  4. Build every title using a consistent pattern — `{Page-Specific Name} | {Site Name}` — so each title is both unique and branded. Pull `siteMetadata.siteTitle` from a `gatsby-config.js` `useStaticQuery` call inside the component.
  5. Run `gatsby build && gatsby serve` then audit with `<title>` checks in DevTools or a crawler to confirm no two pages share the same title string.
Code · Generic HTML (html)
<head>
  <title>Merino Wool Running Socks – Free UK Shipping | BrandName</title>
</head>

Place inside <head>. Make the title unique per page — include the specific item name and brand.

Images missing altModerate effort

Add descriptive alt text to every product and content image so search engines and screen readers can understand what the image shows.

On Gatsby

  1. Audit every `<img>` tag and every `<GatsbyImage>` / `<StaticImage>` component in your `src/` directory (pages, templates, components) for a missing or empty `alt` attribute. Run: `grep -rn 'alt=""\|alt={""\| <img ' src/` to surface offenders quickly.
  2. For `<StaticImage>` (build-time images in `src/components` or `src/pages`): add a meaningful `alt` prop describing the image content, e.g. `alt="Navy blue running shoes on a white background"`. Never leave it blank unless the image is purely decorative — in that case use `alt=""` intentionally AND add `role="presentation"`.
  3. For `<GatsbyImage>` (runtime/CMS-sourced images): pass the alt text through your GraphQL query result. Add an `altText` (or equivalent) field to every GraphQL query that fetches image data (product images, blog hero images, etc.) and wire it to the `alt` prop.
  4. For plain `<img>` tags (third-party embeds, SVG sprites, etc.): ensure every tag has a non-empty `alt` prop. Add the `jsx-a11y/alt-text` ESLint rule (`eslint-plugin-jsx-a11y` is included with `gatsby-plugin-eslint`) to catch regressions automatically at dev/build time.
  5. Run `gatsby build` and inspect the build output for any ESLint `alt-text` warnings, then verify rendered HTML with browser DevTools or an accessibility checker (e.g. Lighthouse) to confirm every `<img>` in the final HTML has a descriptive `alt` attribute.
  6. For product image grids driven by a CMS or commerce API, ensure the data-fetching layer (GraphQL fragment or REST call) always requests and forwards `altText` / `alt` fields so alt text is never hardcoded and always reflects the actual image content.
Code · Generic HTML (html)
<!-- BEFORE (missing alt — bad) -->
<img src="/images/red-leather-bag.jpg">

<!-- AFTER (descriptive alt — correct) -->
<img src="/images/red-leather-bag.jpg" alt="Red leather crossbody bag with gold buckle clasp">

<!-- Decorative image (intentionally empty alt — correct) -->
<img src="/images/divider-line.png" alt="">

Place this wherever an <img> tag appears in your HTML. Replace the placeholder text with your real description.

Meta description too longQuick win

Shorten your meta description to 150–160 characters so Google displays it in full instead of cutting it off with "…" in search results.

On Gatsby

  1. Locate every page/template file that sets a meta description — common locations are `src/pages/.jsx`, `src/templates/.jsx`, and a shared `src/components/Seo.jsx` (or `Seo.js`) component.
  2. Create or update a `truncateDescription` utility function (e.g. in `src/utils/seo.js`) that hard-caps the description at 155 characters on the last full word, keeping it within the 150–160 Google display window.
  3. In your shared `<Seo>` component (Gatsby Head API export or `react-helmet`), pass every description through `truncateDescription` before injecting it into the `<meta name='description'>` tag.
  4. If you use the *Gatsby Head API* (Gatsby ≥ 4.19), export a named `Head` function from each page/template and apply the utility there. If you still use `react-helmet`, apply it inside the `<Helmet>` block.
  5. For pages driven by GraphQL (e.g. product/collection templates), ensure the raw `description` field pulled from your CMS/commerce API is also passed through the same utility so truncation is consistent across all dynamic routes.
  6. Run `gatsby build && gatsby serve` and verify with a `<meta>` inspector or the Google Rich Results Test that no description exceeds 160 characters.
Code · Generic HTML (html)
<meta name="description" content="Award-winning Bang & Olufsen headphones, speakers and TVs. Visit our Malaysia showroom at The Gardens Mall, Level 2. Free shipping on orders over RM500.">

Place this inside the <head> element of every page. Replace the content value with your page-specific description (max 160 characters).

Meta description too shortModerate effort

Expand your meta description to 120–160 characters so Google displays a compelling, complete snippet in search results that drives more clicks to your store.

On Gatsby

  1. Create (or update) a reusable `Seo` component at `src/components/Seo.jsx` that uses Gatsby's built-in *Head API* (exported `Head` function) — no third-party plugin required for Gatsby v4+. The component enforces a 120–160 character meta description by trimming and padding descriptions passed to it.
  2. In every page file (e.g. `src/pages/index.jsx`, `src/pages/products/{ShopifyProduct.handle}.jsx`, `src/templates/product.jsx`, etc.) export a named `Head` function and render `<Seo>` inside it, passing a `description` prop sourced from your CMS/commerce API (e.g. Shopify `product.description`, Contentful `page.metaDescription`).
  3. For collection/template pages built with `gatsby-node.js` (or the File System Route API), pass the description through `pageContext` or GraphQL so the `Head` export in the template can read it — never hard-code fallback text longer than 160 chars.
  4. Add a `DESCRIPTION_MIN = 120` / `DESCRIPTION_MAX = 160` guard inside the `Seo` component that logs a console warning in development when the resolved description falls outside the range, helping you catch short descriptions at build time.
  5. Run `gatsby build && gatsby serve` and inspect the rendered `<meta name='description'>` in the HTML source of each page to confirm every value is between 120 and 160 characters before deploying.
Code · Generic HTML (html)
<meta name="description" content="Shop the official Nike national team jerseys — advanced Dri-FIT heat tech meets bold heritage design. Free shipping on orders over $75. Explore the full collection now.">

Place this inside the <head> element of your HTML page. Replace the content value with your 120–160 character description.

Missing h1Quick win

Add a single, descriptive H1 tag to every page that currently lacks one, clearly stating the page's main topic.

On Gatsby

  1. Audit every page template under `src/pages/` and `src/templates/` (e.g. `index.js`, `product.js`, `collection.js`, `blog-post.js`) to identify which ones render no `<h1>` element in their JSX.
  2. Create (or open) a shared `PageHeading` component at `src/components/PageHeading.jsx` that renders a semantic `<h1>` with consistent base styling, so every page uses the same element rather than ad-hoc headings.
  3. In each page template, import `PageHeading` and place it as the *first meaningful element* inside the page's top-level container — above product grids, hero images, or body copy — passing a descriptive, keyword-rich `title` prop derived from the page's data (e.g. CMS/GraphQL query result).
  4. For Gatsby's Head API (`export function Head()`), ensure the `<title>` tag value and the `<h1>` text are aligned but not identical — the `<h1>` can be slightly more conversational while the `<title>` targets the exact search keyword.
  5. For dynamically generated pages in `gatsby-node.js` (via `createPage`), pass the heading text through `pageContext` so the template always has a non-empty `title` string to render even before any async data loads.
  6. Run `gatsby build && gatsby serve` and use browser DevTools → Elements panel (or an accessibility tree audit) to confirm exactly one `<h1>` exists per page before deploying.
Code · Generic HTML (html)
<h1>Women's Running Shoes</h1>

Place exactly one of these per page, as the first heading in the main content area.

Missing meta descriptionModerate effort

Write and add a unique meta description (120–160 characters) to every page that is missing one, so Google can display an accurate, compelling snippet in search results.

On Gatsby

  1. Create a reusable `<Seo>` component at `src/components/Seo.jsx` that uses Gatsby's built-in Head API (Gatsby ≥ 4.19) to inject a `<meta name='description'>` tag. Accept a `description` prop and enforce a 120–160 character window with a runtime warning in development.
  2. Export the `<Seo>` component from every page file (and page template) as a named export called `Head` — Gatsby's Head API requires this exact export name at the *page* level so the tag is server-rendered into the `<head>`.
  3. For static pages (`src/pages/*.jsx`), pass a hard-coded, unique description string directly to the `<Seo>` component via the `Head` export.
  4. For dynamic template pages (`src/templates/*.jsx`) that are built from GraphQL data (products, collections, blog posts, etc.), add a `description` field to the page's `graphql` query (e.g. `product.seo.description || product.description`), receive it in `pageContext` or the query `data` prop, and forward it to the `<Seo>` component in the `Head` export.
  5. Add a `siteMetadata.defaultDescription` fallback in `gatsby-config.js` and query it with `useStaticQuery` inside `<Seo>` so pages that omit the prop still render a non-empty description rather than nothing.
  6. Run `gatsby build && gatsby serve` and inspect the rendered HTML (`view-source:`) to confirm `<meta name='description' content='…'>` appears in the static markup of every page, not just after client-side hydration.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <meta name="description" content="Shop our full range of handmade leather wallets. Free shipping on orders over $50. Slim, durable & personalised — order yours today.">
  <title>Handmade Leather Wallets | YourStoreName</title>
</head>

Place this tag inside the <head> element of every HTML page. Keep the content between 120–160 characters.

Missing og descriptionModerate effort

Add an og:description meta tag to every page so that social media platforms display a compelling, accurate preview when your pages are shared.

On Gatsby

  1. Create (or open) a reusable SEO component at `src/components/Seo.jsx`. This component will use Gatsby's built-in Head API (Gatsby 4.19+) to inject tags into `<head>` — no extra library needed.
  2. Accept a `description` prop (plus `title`, `image`, `pathname`) in the component and render an `og:description` `<meta>` tag alongside the standard `description` tag so both are always in sync.
  3. In every page file (e.g. `src/pages/index.jsx`, `src/pages/products/{Product.handle}.jsx`, template files in `src/templates/`) export a named `Head` function that renders `<Seo>` with the page-specific description pulled from your GraphQL page query or CMS data.
  4. For dynamic pages (product, collection, blog templates), wire the `description` prop to the real content field from your GraphQL query — e.g. `data.product.seo.description || data.product.description` — so every URL gets a unique, non-empty value.
  5. Run `gatsby develop` and inspect the HTML source (`view-source:http://localhost:8000/`) or the Elements panel to confirm both `<meta name='description'>` and `<meta property='og:description'>` are present with the correct value on every route.
  6. Optionally add a fallback `siteMetadata.description` in `gatsby-config.js` and fetch it with a `useStaticQuery` hook inside the Seo component so pages that forget to pass a `description` prop never ship an empty tag.
Code · Generic HTML (html)
<head>
  <!-- Open Graph / Social Sharing -->
  <meta property="og:type"        content="website" />
  <meta property="og:url"         content="https://www.example.com/page-url/" />
  <meta property="og:title"       content="Page Title – Brand Name" />
  <meta property="og:description" content="A concise, compelling summary of this page in 100–160 characters that encourages clicks." />
  <meta property="og:image"       content="https://www.example.com/images/social-preview.jpg" />
</head>

Place all Open Graph tags inside the <head> element of every page.

Missing og imageModerate effort

Add an og:image meta tag to every page so that social media platforms and messaging apps display a rich, branded image preview whenever someone shares your store's link.

On Gatsby

  1. Create or open your shared SEO component (e.g. src/components/Seo.jsx). This component will use Gatsby's built-in Head API (Gatsby ≥ 4.19) — no extra library needed.
  2. Accept an `ogImage` prop (absolute URL string). Fall back to a default site-wide OG image stored in `static/` (e.g. `static/og-default.jpg`) whose absolute URL is pulled from `siteMetadata.siteUrl` via a `useStaticQuery` GraphQL call.
  3. Inside the component's JSX (returned from the Head export or rendered inside <Head>), add the four required OG image tags: `og:image`, `og:image:width`, `og:image:height`, and `twitter:image`.
  4. Export the `Seo` component and re-export it as the named `Head` export from every page file (e.g. `src/pages/index.jsx`, `src/pages/products/{product.handle}.jsx`) so Gatsby injects the tags into <head> at build time.
  5. For dynamic pages (product/collection PDPs), pass the page's own image URL as the `ogImage` prop (sourced from your CMS/commerce API during `createPages` or via the page's GraphQL query).
  6. Run `gatsby build && gatsby serve` and validate the tags with the LinkedIn Post Inspector or Facebook Sharing Debugger — confirm `og:image` resolves to a publicly reachable absolute URL.
Missing og titleQuick win

Add an og:title meta tag to every page so your store's title appears correctly when shared on social media platforms like Facebook, LinkedIn, and Pinterest.

On Gatsby

  1. Install or confirm `gatsby-plugin-react-helmet` and `react-helmet` are present, OR use Gatsby's built-in Head API (Gatsby ≥ v4.19) — prefer the Head API as it requires no extra dependencies.
  2. For every page component (e.g. `src/pages/index.jsx`, `src/pages/product.jsx`, `src/templates/product-template.jsx`, etc.), export a named `Head` component from the same file. This is the Gatsby Head API entry point.
  3. Inside the `Head` export, render a `<meta property='og:title' content={...} />` tag. Pull the title value from the page's `pageContext`, a GraphQL `data` prop, or a site-wide constant — whichever is already used for the HTML `<title>` tag, so both stay in sync.
  4. Create (or update) a reusable `src/components/Seo.jsx` component that accepts a `title` prop and renders both `<title>`, `<meta name='title'>`, and `<meta property='og:title'>` together. Export it and use it inside every page's `Head` export to keep the logic DRY.
  5. For pages built from GraphQL + `createPage` (templates), ensure the `pageContext` or the page-level GraphQL query already fetches the product/collection/article title, then pass it as the `title` prop to the `<Seo>` component inside `Head`.
  6. Validate the fix by running `gatsby build && gatsby serve`, then inspecting the rendered HTML `<head>` for `<meta property="og:title">` — also paste a page URL into the Facebook Sharing Debugger (developers.facebook.com/tools/debug) to confirm the tag is read correctly.
Code · Generic HTML (html)
<head>
  <!-- Other head tags -->
  <meta property="og:title" content="Amazing Product Name | Your Store Name" />
  <meta property="og:type" content="website" />
  <meta property="og:url" content="https://www.yourstore.com/page-url" />
  <meta property="og:description" content="Short description of this page for social sharing." />
  <!-- Other head tags -->
</head>

Place inside the <head> section of every HTML page. Replace the content value with your actual page title.

Multiple h1Moderate effort

Remove all but one H1 tag from each page so every page has exactly one clear, descriptive primary heading.

On Gatsby

  1. Audit every page and template component (typically in src/pages/ and src/templates/) for multiple <h1> elements. Use a global search (grep -rn '<h1' src/) to find all occurrences across your codebase.
  2. In each page/template, keep exactly ONE <h1> that represents the primary, descriptive heading of that page (e.g. product name, collection title, blog post title). Demote all secondary <h1> tags to the appropriate semantic level: <h2> for section headings, <h3> for sub-sections, etc.
  3. For reusable layout components (src/components/Layout.jsx, Header.jsx, Hero.jsx, etc.) that may render their own <h1> (e.g. a site tagline or banner), convert those to <p>, <span>, or a lower heading level so they never compete with the page-level <h1>.
  4. If you generate pages programmatically via gatsby-node.js (createPage), open the assigned template file (e.g. src/templates/product.jsx) and ensure the single <h1> is driven by the page's dynamic data (e.g. product.title), with no additional hard-coded <h1> elsewhere in the template tree.
  5. Add a development-time assertion (see snippet below) using Gatsby's gatsby-browser.js onRouteUpdate hook to warn in the console whenever more than one <h1> is detected on a rendered page — this catches regressions early without affecting production builds.
  6. Run gatsby build && gatsby serve (or gatsby develop) and use browser DevTools or an accessibility tree inspector to confirm each page has exactly one <h1> before deploying.
Code · Generic HTML (html)
<!-- CORRECT: One H1 per page -->
<h1>Blue Running Shoes – Men's Lightweight Trainers</h1>

<!-- Major sections use H2 -->
<h2>Product Details</h2>
<p>Breathable mesh upper, responsive foam midsole...</p>

<h2>Customer Reviews</h2>
<!-- Sub-sections within a major section use H3 -->
<h3>Top Review: 5 Stars</h3>
<p>Best shoes I've ever bought!</p>

<!-- WRONG: Multiple H1 tags — do NOT do this -->
<!--
<h1>Blue Running Shoes</h1>
<h1>Product Details</h1>
<h1>Customer Reviews</h1>
-->

Correct semantic heading structure for an ecommerce page — one H1, everything else H2 or lower.

Non self canonicalQuick win

Audit every page whose canonical tag points to a different URL, then either correct it to self-reference or confirm the cross-reference is intentional and correct.

On Gatsby

  1. Centralize your site origin in gatsby-config.js under siteMetadata.siteUrl (e.g. `siteUrl: 'https://www.example.com'`) so every canonical is derived from one source of truth.
  2. Replace any ad-hoc <Helmet> canonical tags with Gatsby's built-in Head API: export a named `Head` function from every page file (pages/ and templates/) that constructs the canonical from `siteUrl + location.pathname`, stripping trailing slashes consistently.
  3. In each page template (e.g. src/templates/product.jsx, src/templates/collection.jsx) destructure `pageContext` and `location` props inside the `Head` export, build `canonicalUrl` as `siteUrl + location.pathname` (never from a CMS field unless you explicitly validate it matches the rendered URL), then render `<link rel='canonical' href={canonicalUrl} />`.
  4. For paginated routes (e.g. /collection/shoes?page=2) strip the query string from the canonical so it always points to the base paginated URL or page-1, whichever is your chosen strategy — use `location.pathname` only, not `location.search`.
  5. Add a Gatsby SSR/Browser audit helper: in gatsby-ssr.jsx implement `onRenderBody` to log any page where the injected canonical href does NOT start with siteUrl, catching cross-origin canonicals at build time.
  6. Run `gatsby build && gatsby serve` and spider the output with a tool (e.g. `npx @sitespeed.io/sitespeed` or a custom script) to assert every `<link rel='canonical'>` matches its own page URL before deploying.
Code · Generic HTML (html)
<link rel="canonical" href="https://www.example.com/your-page-url/">

Place this tag inside the <head> element of every page. The href must exactly match the page's own preferred URL.

Page not foundModerate effort

Fix broken internal links pointing to pages that return a 404 error by either removing those links or setting up a 301 redirect from the missing URL to the correct replacement page.

On Gatsby

  1. Audit all internal links by running a crawl (e.g. `gatsby build && npx broken-link-checker http://localhost:9000 -ro`) or using a tool like Screaming Frog to produce a definitive list of broken href values and their correct replacement URLs.
  2. For every broken URL that has a valid replacement, add a 301 redirect entry to `gatsby-config.js` inside the `siteMetadata`-level `createRedirect` calls — or, if using Gatsby Cloud / Netlify / Vercel, export them from `gatsby-node.js` using the `createRedirect` action inside `exports.createPages`.
  3. For broken links where no replacement exists, find every component or page template that renders the dead href (typically inside `src/components/` or `src/pages/`) and remove or replace the `<Link>` / `<a>` element entirely.
  4. Create a custom `src/pages/404.js` (Gatsby's built-in 404 page) so that any undiscovered broken links degrade gracefully and are surfaced clearly to users and crawlers.
  5. Re-run `gatsby build` and repeat the link audit to confirm zero 404 responses remain before deploying.
Code · Generic HTML (html)
<!-- BEFORE: broken internal link -->
<a href="/old-products/discontinued-item">Shop Now</a>

<!-- AFTER: updated to correct URL -->
<a href="/products/replacement-item">Shop Now</a>

Update an internal anchor link to point to the correct URL instead of the broken one.

Redirect chainModerate effort

Update every internal link that passes through a redirect chain to point directly at the final destination URL, eliminating all intermediate hops.

On Gatsby

  1. Audit all redirect chains: run a crawl (e.g. Screaming Frog, `gatsby-plugin-sitemap` output, or a custom Node script) against your built site to enumerate every internal URL that resolves through 2+ hops. Export the mapping of `stale_url → final_destination_url` into a JSON file (e.g. `src/data/redirect-map.json`) that your build can consume.
  2. Centralise the canonical URL resolver: create `src/utils/resolveUrl.js`. It imports `redirect-map.json` and exports a `resolveUrl(href)` helper that walks the map until it finds a URL with no further redirect, returning the final destination. All internal `<Link>` usages will call this helper so stale paths can never slip through.
  3. Replace every raw internal `<Link to={href}>` / `<a href={href}>` across page templates, components, and MDX/Markdown-sourced content with `<Link to={resolveUrl(href)}>`. Prioritise high-traffic components first: `src/components/Header.jsx`, `src/components/Footer.jsx`, `src/components/ProductCard.jsx`, navigation menus, breadcrumbs, and any component that renders CMS-sourced rich text.
  4. Patch `gatsby-node.js` (or `gatsby-config.js`) to remove or consolidate `createRedirect` calls whose `fromPath` is itself a destination of another `createRedirect`, so Gatsby's own redirect table no longer creates chains at the platform level.
  5. Add a build-time validation step in `gatsby-node.js` (`onPostBuild` lifecycle): iterate every page's internal links (parsed from the HTML output in `public/`) and assert none of them appear as a key in `redirect-map.json`. Fail the build with a descriptive error if a stale link is found, preventing regressions.
  6. Run `gatsby build && gatsby serve`, re-crawl the local server, and confirm zero redirect chains remain. Update `redirect-map.json` and re-run this process whenever new redirects are added to the commerce platform or CMS.
Code · Generic HTML (html)
<!-- BEFORE: link points to an intermediate URL that itself redirects -->
<a href="https://example.com/old-page">Our Story</a>

<!-- AFTER: link points directly to the final destination URL -->
<a href="https://example.com/about-us">Our Story</a>

Replace every intermediate redirect URL in your anchor href with the final destination URL — the one that returns HTTP 200 with no further redirects.

Seo category description missingModerate effort

Write a 100–200 word description for every category page explaining what products it contains and who they are for, so Google can rank it for category-level keywords.

On Gatsby

  1. Open (or create) your category page template, typically located at src/templates/category.jsx (or src/pages/[category].jsx if using the File System Route API). This is where category data is fetched and rendered.
  2. In your GraphQL page query (or gatsby-node.js createPage context), ensure the category object includes a `description` field sourced from your CMS/commerce backend. If the field is empty, fall back to a programmatically generated string using the category name and product count.
  3. Render the description in the page body inside a clearly marked <section> or <div> element (e.g., below the hero/banner, above the product grid) so both users and Googlebot can read it as real, crawlable HTML — never hide it with CSS.
  4. Add the description to the page <head> as a meta description using Gatsby's Head API export (gatsby >= 4.19) or react-helmet. Keep the meta description to 155 characters (truncate if needed), while the visible body copy can be the full 100–200 words.
  5. Verify the output with `gatsby build && gatsby serve`, inspect the rendered HTML to confirm the description text appears in the DOM and in the <meta name='description'> tag, then run a Lighthouse/Google Rich Results check.
Code · Generic HTML (html)
<section class="category-description">
  <h1>Women's Waterproof Hiking Boots</h1>
  <p>
    Our women's waterproof hiking boots are built for trail runners, weekend
    trekkers, and serious mountaineers who refuse to let wet weather slow them
    down. Every pair in this collection features a fully sealed waterproof
    membrane, aggressive rubber outsoles for grip on loose or muddy terrain,
    and reinforced ankle support to protect against sprains on uneven ground.
    Whether you're day-hiking a local forest trail or taking on a multi-day
    alpine route, you'll find boots here rated for every conditions and
    experience level. Lightweight options keep pack weight low on fast-and-light
    adventures, while insulated models keep feet warm below freezing. Browse the
    full range below to find your perfect fit.
  </p>
</section>

Place this block near the top of your category page, above the product grid.

Seo product description missingModerate effort

Write and publish a 200–400 word product description covering what the product is, its key specs, who it's for, and how to use it.

On Gatsby

  1. Ensure your product description field is populated in your CMS/commerce backend (e.g. Contentful, Shopify, Sanity) with 200–400 words covering: what the product is, key specs, who it's for, and how to use it. This content is fetched at build time via GraphQL.
  2. Open (or create) your product page template, typically at src/templates/product.jsx or src/pages/products/{id}.jsx. Add a <ProductDescription> section in the JSX body that renders the rich-text or plain-text description pulled from your GraphQL query.
  3. Export a Gatsby Head API function (named `Head`) from the same file to inject the SEO <meta> tags. Use the first ~155 characters of the product description as the meta description. This replaces or augments any legacy react-helmet usage.
  4. Extend your existing page GraphQL query (pageQuery) to select the description field from your data source so it flows into both the page body and the Head export.
  5. Run `gatsby develop` and inspect the rendered HTML to confirm the description text appears inside a <div> in the page body AND as a <meta name='description'> tag in <head>. Then run `gatsby build` to verify the static output.
Code · Generic HTML (html)
<section class="product-description">
  <h2>About This Product</h2>
  <p>
    The <strong>Trailblazer 500 Waterproof Hiking Boot</strong> is a lightweight,
    full-grain leather boot built for all-day comfort on rugged mountain trails.
    Its Gore-Tex membrane keeps feet dry in rain and creek crossings while the
    Vibram outsole delivers confident grip on wet rock and loose scree.
  </p>
  <p>
    Designed for day hikers and weekend backpackers who want reliable protection
    without the weight of a mountaineering boot, the Trailblazer 500 strikes the
    ideal balance between support and agility. The padded collar and gusseted
    tongue prevent trail debris from entering the boot, and the cushioned EVA
    midsole absorbs impact mile after mile.
  </p>
  <h3>Key Specifications</h3>
  <ul>
    <li><strong>Upper:</strong> Full-grain leather with Gore-Tex waterproof lining</li>
    <li><strong>Outsole:</strong> Vibram Megagrip rubber</li>
    <li><strong>Weight:</strong> 680 g / pair (size UK 9)</li>
    <li><strong>Shaft height:</strong> Mid (ankle) cut</li>
    <li><strong>Available widths:</strong> Standard and Wide</li>
  </ul>
  <h3>How to Use</h3>
  <p>
    Lace firmly for ankle support on uneven terrain. Break the boot in over two
    or three shorter walks before a full-day hike. Apply a water-repellent spray
    to the leather upper every 20–30 hours of trail use to maintain waterproofing.
  </p>
</section>

Minimum viable product description markup — paste inside your product page's <main> or article element.

Seo product missing stock statusModerate effort

Add a visible stock availability label and an `availability` property to your Product structured data so Google can display rich results and shoppers know immediately whether a product is in stock.

On Gatsby

  1. Open your product page template (e.g. src/templates/product.jsx or src/pages/products/{handle}.jsx). This is where both the Head export and the rendered JSX live.
  2. Import Gatsby's Head API export (named `Head`) alongside your page component. Inside the `Head` export, build a JSON-LD <script> tag that includes the `offers.availability` property mapped from your CMS/commerce stock field (e.g. `availableForSale`, `stockStatus`, `quantityAvailable`).
  3. Map your commerce platform's stock value to the correct schema.org URL: `https://schema.org/InStock`, `https://schema.org/OutOfStock`, or `https://schema.org/PreOrder`. Never hard-code a single value — derive it dynamically from the page's data prop.
  4. In the page component's JSX, render a visible stock-status label (e.g. a <span> or <p>) so shoppers and assistive technologies can read the availability without relying solely on structured data. Style it with a clear colour convention (green for in-stock, red for out-of-stock).
  5. Run `gatsby build && gatsby serve` and validate the JSON-LD output with Google's Rich Results Test (https://search.google.com/test/rich-results) to confirm the `availability` field is present and resolves to a valid schema.org URL.
  6. If your stock data is not yet fetched in the GraphQL page query, extend the query to include the relevant availability field (e.g. `availableForSale` from Shopify Storefront API or equivalent) so the Head export always receives live data.
Code · Generic HTML (html)
<!-- Product structured data with availability -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Your Product Name",
  "description": "A short description of your product.",
  "sku": "YOUR-SKU-123",
  "image": "https://www.example.com/images/product.jpg",
  "brand": {
    "@type": "Brand",
    "name": "Your Brand Name"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/products/your-product",
    "priceCurrency": "USD",
    "price": "29.99",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>

<!-- Visible stock label near Add-to-Cart button -->
<p class="stock-status" style="font-weight:bold; color:#2a7a2a;">&#10003; In Stock</p>

Paste the complete block into the <head> section of your product page HTML. Change availability value to OutOfStock or PreOrder as needed.

Seo product no imagesModerate effort

Add at least one high-quality image to every product page so shoppers can see what they're buying and Google can surface your products in Image Search.

On Gatsby

  1. In your product page template (typically src/templates/product.jsx or src/pages/products/[handle].jsx), ensure your GraphQL query fetches at least one image per product — include `images { src altText }` (or the equivalent fields from your commerce source plugin, e.g. Shopify: `featuredImage { url altText }`).
  2. Render the primary product image using Gatsby's `<GatsbyImage>` component (from `gatsby-plugin-image`) when the image is sourced through Gatsby's image pipeline, or a plain `<img>` tag with explicit `width`, `height`, and a descriptive `alt` attribute when the URL comes directly from a headless commerce API.
  3. Guard against missing images: show a branded placeholder image when `product.images` is empty so no product page is ever imageless.
  4. Export a `Head` component from the same page/template file and add `<meta property='og:image'>` plus `<meta name='twitter:image'>` pointing to the primary product image URL, enabling Google to discover the image via Open Graph as well as direct crawl.
  5. Add a Product JSON-LD `<script type='application/ld+json'>` block inside the `Head` export that includes the `image` field, giving Google structured-data confirmation of the product image.
  6. Run `gatsby build && gatsby serve` and validate with Google's Rich Results Test and the URL Inspection tool in Search Console to confirm the image is indexed.
Code · Generic HTML (html)
<figure class="product-gallery">
  <img
    src="/images/products/black-leather-wallet-front.jpg"
    alt="Black leather wallet with RFID blocking, front view"
    width="800"
    height="800"
    loading="eager"
  />
  <img
    src="/images/products/black-leather-wallet-open.jpg"
    alt="Black leather wallet open showing card slots and cash pocket"
    width="800"
    height="800"
    loading="lazy"
  />
</figure>

Basic responsive product image with descriptive alt text — place inside your product page HTML where the gallery/main image appears.

Seo product no spec tableModerate effort

Add a structured specifications table or list to every product page so shoppers, search engines, and AI tools can instantly find and use key product details.

On Gatsby

  1. In your product page template (typically `src/templates/product.js` or `src/pages/products/{handle}.js`), locate where product data is queried via GraphQL (gatsby-node or file-system routing). Ensure your query fetches all specification fields (e.g., weight, dimensions, material, SKU, colour, etc.) from your CMS/commerce source.
  2. Create a reusable `ProductSpecsTable` component at `src/components/ProductSpecsTable.jsx` that accepts a `specs` prop (array of `{ label, value }` objects) and renders a semantic `<table>` with proper `<th>` and `<td>` markup so crawlers and AI parsers can read it as structured data.
  3. Import and render `<ProductSpecsTable>` inside your product page template, passing the fetched specification fields as the `specs` array, placed visibly in the page body (not hidden or behind a tab that requires JS interaction to expose).
  4. Add a matching `Product` JSON-LD `<Script type='application/ld+json'>` block (using Gatsby's `<Script>` component from `gatsby`) inside the Gatsby Head API export (`export function Head()`) of your product template. Include the spec fields inside `additionalProperty` using `PropertyValue` schema so search engines and AI tools can parse them in structured form.
  5. Verify the table is accessible: use a `<caption>` element describing the table, ensure `scope='row'` on label cells, and confirm sufficient colour contrast so the fix also helps AEO (answer engine optimisation) tools that parse visible page text.
  6. Run `gatsby build && gatsby serve` and validate both the rendered HTML table and the JSON-LD with Google's Rich Results Test and a manual DOM inspection to confirm specs are present in the static output.
Seo product price not in htmlModerate effort

Render your product price in the server-generated HTML so Google and other crawlers can read it directly — without executing JavaScript.

On Gatsby

  1. In gatsby-node.js (or the File System Route API file src/pages/products/{Product.slug}.jsx), ensure each product page is created at BUILD TIME via createPages or the FSR API, pulling price data from your commerce API/CMS so it is available as page context or as a GraphQL node.
  2. In your product page template (e.g. src/templates/product.jsx or src/pages/products/{Product.slug}.jsx), export a GraphQL page query (or use pageContext) that includes the price fields — amount, currencyCode, compareAtPrice — so Gatsby injects them as props.data or props.pageContext at SSG time.
  3. Replace any client-side-only price rendering (useEffect, useState, dynamic fetch after hydration) with direct use of the price value coming from props.data / props.pageContext, so the price is present in the static HTML Gatsby outputs.
  4. Render the price inside semantic HTML — a <span> or <p> with a machine-readable itemprop='price' attribute — so both crawlers and screen-readers can consume it without JS.
  5. Add JSON-LD Product structured data via Gatsby Head API (exported Head function) or react-helmet, including 'offers.price' and 'offers.priceCurrency', so Google's Rich Results can also pick up the price.
  6. Run gatsby build && gatsby serve and inspect the page source (Ctrl+U / View Source, NOT DevTools Elements) to confirm the price string appears in the raw HTML before any JS executes.
Seo product title genericModerate effort

Rewrite every product page title tag to include the specific product name plus at least one differentiating attribute (variant, material, brand, or model) so Google and shoppers can instantly tell what the page is about.

On Gatsby

  1. Locate your product page template — typically `src/templates/product.jsx` (or `.tsx`) — where each product detail page is generated via `gatsby-node.js` / File System Route API (`src/pages/products/{Product.handle}.jsx`).
  2. Inside the template, destructure the fields you need from the page query result: `title`, `productType` (or `vendor`/`variants`), and any differentiating attributes such as `variants[0].title` (size/colour), `material`, or `brand`.
  3. Build a rich, specific title string in the component body, e.g. `const pageTitle = \`${product.title} – ${product.variants[0].title} | ${product.vendor}\`` — choose whichever attributes make the product unique.
  4. Use *Gatsby Head API* (Gatsby ≥ 4.19) by exporting a named `Head` function from the same template file — this is the modern, recommended approach and avoids the `react-helmet` peer-dependency issues.
  5. Inside the `Head` export, render a `<title>` tag and a matching `<meta name='description'>` tag using the constructed string so both the SERP title and snippet are specific to the product.
  6. Verify output by running `gatsby build && gatsby serve` and inspecting the rendered HTML `<head>` for each product route; confirm the title contains the product name plus at least one differentiating attribute.
Code · Generic HTML (html)
<head>
  <title>Magnetic Card Holder Phone Mount – MagSafe Compatible – BrandName</title>
  <meta name="description" content="Attach your cards securely to any MagSafe-compatible iPhone with BrandName's slim magnetic card holder mount. Holds 1–3 cards. Available in black and white.">
</head>

Place inside the <head> section of every product page. Follow the pattern: Product Name – Key Attribute – Brand.

Title too longQuick win

Shorten your page title tag to 60 characters or fewer so it displays fully — without truncation — in Google search results.

On Gatsby

  1. Identify every page/template file that sets a <title> — check both the modern Gatsby Head API exports (export function Head()) and any legacy react-helmet <Helmet> components across src/pages/, src/templates/, and shared layout/SEO components (e.g. src/components/Seo.jsx).
  2. Create (or update) a shared utility helper — e.g. src/utils/truncateTitle.js — that enforces a ≤60-character hard limit, preserving the most meaningful part of the title (typically the product/page name) and appending a short brand suffix only when space allows.
  3. In every page/template that uses the Gatsby Head API, import the helper and pass the truncated string to the <title> tag and the og:title <meta> tag inside the exported Head() function.
  4. For any remaining legacy react-helmet usage, apply the same helper inside the <Helmet> component's title prop and the og:title meta content prop.
  5. For programmatic pages (gatsby-node.js / gatsby-config.js), ensure the pageContext or GraphQL data feeding titles is also passed through the truncator so SSR-rendered HTML is already correct at build time.
  6. Run gatsby build && gatsby serve and use browser DevTools → Elements to verify <title> is ≤60 chars on product, collection, blog, and home pages; also confirm og:title matches.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <title>Running Shoes for Men | YourBrand</title>
</head>

Place inside the <head> element of every HTML page. Keep the total character count at or under 60.

Title too shortQuick win

Expand your page title to between 30 and 60 characters so Google displays it fully in search results and shoppers can immediately understand what the page is about.

On Gatsby

  1. Identify every page component (e.g. src/pages/.jsx, src/templates/.jsx) whose <title> is too short — run a quick audit with your browser DevTools or a crawl tool to list them.
  2. Use Gatsby's built-in Head API (Gatsby 4+) — export a named `Head` function from the same page/template file. If you are on Gatsby 3 or earlier, use `react-helmet` via the `gatsby-plugin-react-helmet` package instead.
  3. Craft a descriptive title string that is 50–60 characters: include the primary keyword, a value differentiator (e.g. 'Free Shipping', 'Official Store'), and the brand name, separated by a pipe or dash.
  4. Pass dynamic values (product name, category name, etc.) from the page's `data` prop (GraphQL query result) into the title so every page gets a unique, keyword-rich string rather than a hard-coded fallback.
  5. Add a 50–60 char `<meta name='description'>` in the same Head export so Google has a complete snippet to display alongside the title.
  6. Run `gatsby build && gatsby serve` and verify the rendered `<title>` in View Source — confirm the character count is between 50 and 60.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <title>Men's Merino Wool Beanies – BrandName</title>
</head>

Place inside the <head> element of every page. Replace the example with your actual descriptive title (30–60 chars).

Answer Engine Optimization · 3 fixes

Missing schema breadcrumblistModerate effort

Add BreadcrumbList structured data (JSON-LD) to every page so Google can display your site's navigation path directly in search results.

On Gatsby

  1. Create a reusable `BreadcrumbListSchema` component in `src/components/BreadcrumbListSchema.jsx` that accepts a `crumbs` array prop (each item: `{ name, url }`) and renders a JSON-LD `<script>` tag using Gatsby's built-in `<Script>` component (or a plain `<script>` tag inside Gatsby Head API's `<Head>` export).
  2. In every page file (e.g. `src/pages/index.jsx`, `src/templates/product.jsx`, `src/templates/category.jsx`), export a named `Head` function — Gatsby's Head API (Gatsby ≥ 4.19) — and render `<BreadcrumbListSchema>` inside it with the page-specific breadcrumb trail.
  3. Build the `crumbs` array dynamically from page context data already passed by `gatsby-node.js` (e.g. `pageContext.breadcrumbs`) so the data is available at build-time (SSG) or per-request (DSG/SSR).
  4. For deeply nested templates, pass breadcrumb data through `gatsby-node.js` → `createPage()` → `context` so every generated page automatically receives its own trail without extra fetch calls.
  5. Validate the output by running `gatsby build && gatsby serve`, inspecting the page source for the `<script type='application/ld+json'>` block, and testing the URL in Google's Rich Results Test (https://search.google.com/test/rich-results).
Code · Generic HTML (json)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://www.example.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Women's Clothing",
      "item": "https://www.example.com/womens-clothing/"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Dresses",
      "item": "https://www.example.com/womens-clothing/dresses/"
    },
    {
      "@type": "ListItem",
      "position": 4,
      "name": "Blue Wrap Dress",
      "item": "https://www.example.com/womens-clothing/dresses/blue-wrap-dress/"
    }
  ]
}
</script>

Place this <script> block inside <head> or anywhere in <body> on any page with a breadcrumb trail. Extend itemListElement with one ListItem per level.

Missing schema organizationQuick win

Add an Organization schema markup block to your store's homepage (and/or sitewide template) so search engines and AI assistants can definitively identify your brand.

On Gatsby

  1. Open (or create) your homepage component, typically `src/pages/index.jsx` (or `index.tsx`). This is where the Organization schema will live because it represents the root of your domain.
  2. Import Gatsby's `Script` component (Gatsby ≥ 4.15) for injecting the JSON-LD block, and the `Head` export (Gatsby Head API, Gatsby ≥ 4.19) to keep it server-side rendered and statically included in the HTML `<head>`.
  3. Create and export a named `Head` function from your homepage file. Inside it, render a `<script type='application/ld+json'>` tag (via the native `<script>` element inside `Head`) containing the full `Organization` JSON-LD object — populate `name`, `url`, `logo`, `sameAs`, `contactPoint`, etc. with your real store values.
  4. Populate `sameAs` with every social-profile and directory URL (Facebook, Instagram, Twitter/X, LinkedIn, Pinterest, TikTok, Google Business Profile, etc.) so AI systems can unambiguously link all your brand presences.
  5. Run `gatsby develop` and inspect the rendered `<head>` in DevTools (or view-source) to confirm the `<script type='application/ld+json'>` block appears in the initial HTML — NOT injected after hydration.
  6. Validate the output with Google's Rich Results Test (https://search.google.com/test/rich-results) and Schema.org's validator (https://validator.schema.org/) before deploying to production.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Acme Store",
  "url": "https://www.acmestore.com",
  "logo": {
    "@type": "ImageObject",
    "url": "https://www.acmestore.com/images/logo.png",
    "width": 250,
    "height": 250
  },
  "sameAs": [
    "https://www.facebook.com/acmestore",
    "https://www.instagram.com/acmestore",
    "https://twitter.com/acmestore",
    "https://www.linkedin.com/company/acmestore",
    "https://www.youtube.com/c/acmestore"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "contactType": "customer service",
    "email": "hello@acmestore.com"
  }
}
</script>

Drop this complete block inside your <head>…</head> section on every page. Replace all placeholder values.

Missing schema webpageModerate effort

Add WebPage schema markup to every page so search engines and AI answer engines can precisely understand and describe your content.

On Gatsby

  1. Create (or open) a reusable SEO component at `src/components/Seo.jsx`. This component will use Gatsby's *Head API* (exported `Head` function, available in Gatsby v4.19+) to inject JSON-LD `<script>` tags — no third-party helmet library needed.
  2. Inside `Seo.jsx`, compose two JSON-LD objects: a `WebPage` schema (using props like `title`, `description`, `url`, and `breadcrumb`) and an `Organization` schema (using your site's name, logo URL, and canonical URL). Merge them into a JSON-LD `<script type='application/ld+json'>` tag.
  3. Pull global site metadata (name, siteUrl, logo, social links) from `gatsby-config.js` via a `useSiteMetadata` custom hook that calls Gatsby's `useStaticQuery` + `graphql` — this keeps values DRY and sourced from one place.
  4. In every page file (e.g. `src/pages/index.jsx`, `src/pages/products/[handle].jsx`, collection pages, blog posts, etc.) export a named `Head` function that renders `<Seo />` with the page-specific props. Gatsby automatically hoists whatever the `Head` export returns into `<head>`.
  5. For dynamic/template pages created via `gatsby-node.js` (`createPage`), pass `title`, `description`, `url`, and optional `breadcrumb` through the page's `pageContext` so the template's `Head` export can forward them to `<Seo />`.
  6. Run `gatsby build && gatsby serve`, open DevTools → View Page Source, and confirm `<script type='application/ld+json'>` blocks containing `@type: WebPage` and `@type: Organization` appear in the static HTML of every page. Validate with Google's Rich Results Test or schema.org validator.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "WebPage",
  "name": "About Us | Acme Store",
  "description": "Learn about Acme Store, our mission, and the team behind our products.",
  "url": "https://www.example.com/about/",
  "inLanguage": "en-US",
  "datePublished": "2022-01-15",
  "dateModified": "2024-06-01",
  "isPartOf": {
    "@type": "WebSite",
    "name": "Acme Store",
    "url": "https://www.example.com/"
  },
  "breadcrumb": {
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Home",
        "item": "https://www.example.com/"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "About Us",
        "item": "https://www.example.com/about/"
      }
    ]
  }
}
</script>

Paste this inside the <head> element of any HTML page. Replace all placeholder values with real page data.