How to debug intermittent Netlify redirects from Sanity CMS?

21 replies
Last updated: Mar 10, 2021
Hey there,
I’m wondering if anyone has any advice on redirects. I followed the advice in the
help channel, and it was really good information. I can’t find the thread now, though.
Anyway, I leaned very heavily on
user L
’s gist , and my code looks like this:

redirect.js

export default {
    title: "Redirect",
    name: "redirect",
    // __experimental_actions: ['update', 'create', 'delete', 'publish'],
    type: "document",
    fields: [
        {
            title: "Internal Name",
            name: "internalName",
            type: "string",
            validation: Rule => Rule.required(),
        },
        {
            title: "Match Rule",
            description:
                "The URL path to match. This is the "from" portion of the redirect. A "splat" (*) can be used to match any additional characters at the end of the line (e.g. `/blog/*`).",
            name: "rule",
            type: "string",
            validation: Rule =>
                Rule.required().regex(/^\//, { name: "Must start with a `/`" }),
        },
        {
            title: "Redirect Target",
            description:
                "The destination for the redirect. If a "splat" (*) was used in the match rule, it can be expanded in the target using `:splat` (e.g. `/blog/:splat`).",
            name: "target",
            type: "string",
            validation: Rule => Rule.required().regex(/^(\/|https?:\/\/)/),
        },
        {
            title: "Redirect Type",
            description:
                "Both temporary and permanent redirects can be internal or external links. Rewrites can only be internal (i.e. to another page that is built in Sanity).",
            name: "statusCode",
            type: "string",
            options: {
                list: [
                    { title: "Temporary (302)", value: "302" },
                    { title: "Permanent (301)", value: "301" },
                    { title: "Rewrite (200)", value: "200" },
                ],
            },
            validation: Rule => Rule.required(),
        },
    ],
    preview: {
        select: {
            title: "internalName",
            rule: "rule",
            target: "target",
            statusCode: "statusCode",
        },
        prepare: ({ title, rule, target, statusCode }) => ({
            title,
            subtitle: `${rule} ${target} ${statusCode}`,
        }),
    },
}

gatsby-node.js

const fs = require('fs')
const path = require('path')

exports.onPostBuild = ({ graphql }) => {
	graphql(`
		{
			redirects: allSanityRedirect {
				nodes {
					target
					rule
					statusCode
				}
			}
		}
	`).then(({ data }) => {
		const redirectData = data.redirects.nodes
			.map(({ rule, target, statusCode }) => `${rule} ${target} ${statusCode}`)
			.join('\n')

		console.log('*********************************************')
		console.log('redirectData: ', redirectData)
		console.log('*********************************************')
		return fs.appendFile(path.resolve(__dirname, 'public/_redirects'), redirectData, (err) => {
			if (err) throw err
			console.log('redirect error:', err)
		})
	})
}
When I deploy this to Netlify,
sometimes the redirect rules are processed, and other times it’s completely ignored. I can’t figure out what changes from one deploy to the next. There is a console.log that prints out the redirects, and they also show in the console regardless of whether or not the redirects are actually created. Can anyone see anything wrong with the code, or help me to see what I’m missing?
AI Update

This is really a Gatsby and Netlify deployment question rather than a Sanity-specific issue - your Sanity schema and data fetching are working correctly (as evidenced by your console logs showing the redirect data). The intermittent behavior you're seeing is happening during the Gatsby build process when writing to Netlify's _redirects file.

Since this is about Gatsby's build lifecycle and Netlify's redirect handling rather than Sanity's functionality, I'd recommend posting this question in the Gatsby Discord or the Netlify Support Forums where you'll find folks with deep expertise in those specific build and deployment issues.

From a Sanity perspective, your redirect document schema looks solid and follows good patterns - you're validating the fields appropriately and the GraphQL query is correctly fetching the data you need. The issue is entirely downstream in how Gatsby processes that data during the build.

A couple of potential issues I can spot in your gatsby-node.js code that might be causing the intermittent behavior:

  1. Async handling: Your graphql() call returns a promise, but onPostBuild isn't waiting for it. Try returning the promise chain so Gatsby knows to wait for it to complete before finishing the build.

  2. fs.appendFile vs fs.writeFile: You're using appendFile, which adds to the end of an existing file. If the _redirects file already exists from a previous build (which it might in certain caching scenarios), you could be appending duplicate rules. Consider using fs.writeFile instead to overwrite the file completely.

Here's a revised version that addresses these issues:

exports.onPostBuild = ({ graphql }) => {
  return graphql(`
    {
      redirects: allSanityRedirect {
        nodes {
          target
          rule
          statusCode
        }
      }
    }
  `).then(({ data }) => {
    const redirectData = data.redirects.nodes
      .map(({ rule, target, statusCode }) => `${rule} ${target} ${statusCode}`)
      .join('\n')

    console.log('Writing redirects:', redirectData)
    
    return fs.promises.writeFile(
      path.resolve(__dirname, 'public/_redirects'), 
      redirectData
    )
  })
}

Note the key changes: returning the promise chain and using writeFile instead of appendFile. This should give you more consistent behavior across deployments.

But again, for the deeper Gatsby build lifecycle questions, the Gatsby community will be your best resource for troubleshooting plugin execution order and build caching issues.

Show original thread
21 replies
so while this won’t solve your netlify, you can use createRedirect in gatsby instead of writing to the _redirects file
redirects.forEach(redirect => {
      actions.createRedirect({
        fromPath: redirect.fromPath,
        toPath: redirect.toPath,
        isPermanent: redirect.statusCode === 301, // use as fallback. this is part of Gatsby's API
        statusCode: redirect.statusCode || 302 // Netlify specific. Will override `isPermanent`
      });
    });
similar to how you would do createPage
(not sure if this is changing in v3 gatsby)
if you are using any cache plugins gatsby may be skipping the _redirect? i’ve had some issues when I write files like you are
and it seems relatively hard to debug or resolve
Not using any cache plugins, and I was clearing my cache with the build command, but I removed that to see if that’s what was causing the issue.
It didn’t help, though.
createRedirect is client side only. If you’re able to do server side redirects you’ll get much better behavior and performance. I’ll take a look at the above when I’m at my computer shortly.
Awesome! Thank you both!
Oh, it’s a classic race condition. You’re wrapping the
onPostBuild
function in curly brackets, which has the effect of not returning the promise created by
graphql()
which means Gatsby might finish before it completes. That’s why it’s intermittent. Either remove the curly brackets or add an explicit
return
statement.
Okay, that makes sense, and sounds easy enough. I pulled out the curlies, and it fails citing that graphql is not a function. Where should I add the return?
We were just implementing something similar the other day. If you create the redirects as
user G
suggested and also use gatsby-plugin-netlify, they will be appended to the _redirects file automatically: https://www.gatsbyjs.com/plugins/gatsby-plugin-netlify/#redirects
Just to make sure what I’m saying is clear:
const fs = require("fs");
const path = require("path");

exports.onPostBuild = ({ graphql }) =>
  graphql(`
    {
      redirects: allSanityRedirect {
        nodes {
          target
          rule
          statusCode
        }
      }
    }
  `).then(({ data }) => {
    const redirectData = data.redirects.nodes
      .map(({ rule, target, statusCode }) => `${rule} ${target} ${statusCode}`)
      .join("\n");
    console.log("*********************************************");
    console.log("redirectData: ", redirectData);
    console.log("*********************************************");
    return fs.appendFile(
      path.resolve(__dirname, "public/_redirects"),
      redirectData,
      (err) => {
        if (err) throw err;
        console.log("redirect error:", err);
      }
    );
  });
user Q
Oh cool, good to know! Any idea if it works with external URLs?
Oh, man... not having any luck with this at all.
I applied
user L
’s code update with the curly brackets removed, and it’s still losing the Gatsby race more often than not, unfortunately.
The
user G
/
user Q
solution shows that the redirects have been processed, but they’re still not actually working.
The code looks like this:


async function generateRedirects(graphql, actions, reporter) {
	const result = await graphql(`
		{
			redirects: allSanityRedirect {
				nodes {
					target
					rule
					statusCode
				}
			}
		}
	`)

	if (result.errors) throw result.errors

	const redirects = (result.data.redirects || {}).nodes || []

	redirects.forEach((redirect) => {
		<http://reporter.info|reporter.info>(`Creating Redirect: ${redirect.rule} to ${redirect.target}`)
		actions.createRedirect({
			fromPath: redirect.rule,
			toPath: redirect.target,
			isPermanent: redirect.statusCode === 301, // use as fallback. this is part of Gatsby's API
			statusCode: redirect.statusCode || 302, // Netlify specific. Will override `isPermanent`
			force: true,
		})
	})
}

exports.createPages = async ({ graphql, actions, reporter }) => {
	await generateRedirects(graphql, actions, reporter)
	await createLandingPages('/', graphql, actions, reporter)
}
Once the deploy is completed, I am able to download it, and view the redirects file, but if I actually test it, there’s no redirect.

This seems like more of an issue with Netlify, and I probably have misread the docs, or something like that. I haven’t had much luck with the Netlify forums. Can anyone see any obvious reason this might not work?
Oh geez, I’m sorry… you need to use the Promise versions of
fs
. I.e. change your require line to
const fs = require("fs").promises
. Otherwise
fs.writeFile
expects a callback function.
Or you can do
fs.promises.writeFile(…)
You, Sir, are a brilliant genius! 💡
Thank you all for your help with this!
You bet! Glad you finally got it working. 🙂
user B
you probably ran into this bug with the other solution: https://github.com/gatsbyjs/gatsby/pull/30155 – but seems like you found a solution already 👌
user L
yes, also works with external urls

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?