Converting Astro redirects to Cloudflare redirects

Keep your redirects in one place but benefit from Cloudflare's redirects

Astro provides a built-in mechanism to define redirects in the redirects property of the route object.

export default defineConfig({
	/* ... */
	redirects: {
		'/store': {
			destination: 'https://store.example.com',
			status: 302
		},

		'/old-slug': '/new-slug'
	}
})

When the site is built these redirects turn into a index.html file at the specific path with a HTML meta redirect pointing to the new site. While this is a good solution for simple redirects, it has some drawbacks like the status code will not be returned by the original path, rather it will be 200 and the browser will flash the HTML of the redirect page briefly.

But Cloudflare Pages provides a better way to handle redirects with their _redirects file. This file is a simple text file that lists the redirects in a specific format.

This file has a simple format: /from /to HTTP_STATUS_CODE where the status code is either 301 or 302.

To recreate the same redirects in the _redirects file, you can use the following:

/store https://store.example.com 302
/old-slug /new-slug 301

But wouldn’t it be nice to just maintain the Astro file and have it converted to the _redirects file on build? This way it works on your local site and you don’t have to remember to convert it to the _redirects file format.

To make this work you’ll need a custom integration which runs during the build process. You can use the following script to convert the Astro redirects to the _redirects file format.

// astro-redirect-to-cloudflare-redirect.ts
import type { AstroIntegration } from 'astro'
import { writeFile } from 'fs/promises'
import { fileURLToPath } from 'node:url'

interface RedirectRule {
	source: string
	destination: string
	status?: number
}

export default function redirectsToCloudflare(): AstroIntegration {
	return {
		name: 'astro-redirect-to-cloudflare-redirect',
		hooks: {
			'astro:build:done': async ({ dir, routes }) => {
				console.log('\n🔄 Processing redirects for Cloudflare...')

				// Extract redirects from routes
				const redirects = routes
					.filter((route) => route.type === 'redirect')
					.map((route) => {
						let destination = route.redirect
						let status = 301

						// Check if redirect is an object
						if (typeof route.redirect === 'object' && route.redirect !== null) {
							destination = route.redirect.destination
							status = route.redirect.status || 301
						}

						const rule = {
							source: route.component.toString(),
							destination,
							status
						} as RedirectRule

						console.log(`  ↪️  ${rule.source}${rule.destination} (${rule.status})`)
						return rule
					})

				console.log(`\n📊 Found ${redirects.length} redirects to process`)

				// Format redirects for Cloudflare
				const redirectContent = redirects
					.map(({ source, destination, status }) => `${source} ${destination} ${status}`)
					.join('\n')

				// Write _redirects file
				const outFile = fileURLToPath(new URL('./_redirects', dir))
				await writeFile(outFile, redirectContent, 'utf-8')
				console.log(`✅ Generated Cloudflare redirects file at: ${outFile}`)
			}
		}
	}
}

Save this file to /utils/astro-redirect-to-cloudflare-redirect.ts and then import it and add to the integrations array in your astro.config.mjs file.

import redirectsToCloudflare from './src/utils/astro-redirect-to-cloudflare-redirect'

// https://astro.build/config
export default defineConfig({
	integrations: [
        /* ... */
		redirectsToCloudflare(),
	],
	redirects: {
		/* ... */
	}
})

From this point on you can maintain your redirects in the Astro config and they’ll be converted to the _redirects file on build.