How to Add External Redirects to Your Astro Site on Vercel
Astro supports redirects out of the box when using an adapter, but only redirects to pages on the same site that Astro knows to exist. Although the Cloudflare adapter does support external redirects, supporting them is considered a “non-goal” according to the Astro roadmap. There does seem to be interest in adding external redirects to the Vercel adapter as well, but as of now this doesn’t work.
It’s unfortunate that this isn’t supported out of the box as some people may need redirects to keep
old links from breaking. For now the only way to implement redirects is to define them in your
vercel.json
file, or by using Edge Middleware
if you need to do the redirects more dynamically. I’ll show you how to do this with Vercel in this
article, however these concepts could be applied to other serverless providers as well even if the
implementation will differ.
Defining redirects in your configuration
For most cases you can define your redirects in vercel.json
. Redirects defined here can use
patterns so you don’t have to list every possible URL that you need to redirect.
Here is an example of how a redirect to another website might look:
{
"redirects": [
{
"source": "/posts/:path(.*)",
"destination": "https://<SOME_OTHER_DOMAIN>.com/posts/:path",
"permanent": true
}
],
...
}
However, if you have a more complicated situation where you need to conditionally redirect content off of some other criteria other than the URL alone, then you will need middleware. In my case I had to use middleware as I have some old search engine entries pointing to the wrong site (this one) that need to be cleaned up.
Defining redirects using Edge Middleware
To solve this problem I created stubbed out articles in my Astro content collection for each piece
of external content that I have. Each of these has a special key in its frontmatter with the value
being the fully-qualified URL of the article on the other site. You could call it something like
externalLink
. This link is used both for overriding the URL in my blog post list to point to the
external site and also setting up the redirect so that old links go to the right place.
Here is the middleware that I used to accomplish this:
import redirects from "./redirects.json";
export const config = {
matcher: "/posts/:slug*",
};
export default function middleware(request: Request) {
const url = new URL(request.url);
for (const [path, redirectUrl] of Object.entries(redirects)) {
if (url.pathname === path || url.pathname === path + "/") {
return Response.redirect(redirectUrl, 308); // 308 is a permanent redirect
}
}
}
You will notice that I’m importing redirects from a static file. The redirects.json
is generated
at build time using a postbuild
npm script which runs automatically after every build. I also
limit this middleware to only running against blog URLs since it doesn’t need to run on everything
else, though this is optional.
The postbuild
script needs to identify which URLs belong to external content and which ones do
not by reading the frontmatter. I do this using the gray-matter
library:
// @ts-check
import fs, { readdir } from "fs/promises";
import matter from "gray-matter";
import path from "path";
const SRC_POSTS_DIR = "./src/content/posts";
const REDIRECTS_PATH = "./redirects.json";
/**
* Create a redirects.json file for the middleware with all external links.
*/
async function generateRedirectsFile() {
/** @type {Record<string, string>} */
const redirects = {};
// Gather a list of all files in src/content/posts into an array.
const posts = (await readdir(SRC_POSTS_DIR, { withFileTypes: true })).filter(
(dirent) => dirent.isFile(),
);
for (const post of posts) {
const contentPath = path.join(post.path, post.name);
const content = await fs.readFile(contentPath, { encoding: "utf8" });
const frontmatter = matter(content).data;
if (frontmatter.externalLink) {
// Remove the .mdx extension.
const slug = post.name.slice(0, -4);
// Create the redirect entry in the JSON file.
redirects[`/posts/${slug}`] = frontmatter.externalLink;
}
}
// Save the JSON file to the project root.
await fs.writeFile(REDIRECTS_PATH, JSON.stringify(redirects, null, 2));
}
await generateRedirectsFile();
Don’t forget to modify your package.json
with the postbuild
script entry:
{
"name": "jamiekuppens.com",
"type": "module",
"scripts": {
"dev": "astro dev",
"preview": "astro preview",
"astro": "astro",
"build": "astro build",
"postbuild": "node ./scripts/postbuild.js"
},
...
With that done, redirects should now work once the site gets deployed. Since this is using Vercel this won’t work locally unless the Vercel adapter is being used.
In Summary
Although Astro doesn’t support external redirects out of the box, this can be worked around by using
the features provided by your deployment platform of choice. I doubt many people have to do
redirects like I do and a simple rule in vercel.json
would suffice, but for more complicated
scenarios Edge Middleware is a powerful tool to have at your disposal.
You could also use middleware to configure redirects defined outside of your project using another data source like Edge Config, which would allow you to change redirects without having to rebuild your site.
Anyhow, I hope this article was helpful 😊