Regex based redirects for Static Web Apps
The biggest challenge of moving my blog to Azure Static Web Apps were the redirects. I still support permalinks from my previous blog engine and I previously used back-references in IIS rewrite module to handle redirects for them. Routes in Azure Static Web Apps aren't nearly flexible enough to cover my needs.
During my research I stumbled upon a blog post from John Reilly which shows how redirects can be implemented with managed API Azure Functions using the same approach as serve-side rendered Nuxt apps:
- In
staticwebapp.config.jsonconfiguration file, an Azure Function is configured to be called for paths that can't be resolved to a static file:{ "navigationFallback": { "rewrite": "/api/redirect" } } - In that Azure Function, the original URL is read from the
x-ms-original-urlheader:const originalUrl = request.headers.get("x-ms-original-url");
Most of my redirects could be implemented with a String.replace() method call in JavaScript:
- A regular expression is used to match a route.
- Its capturing groups are then reused in the destination route.
For example, a ^/categories/(.*)$ route would be mapped to /tags/$1.html, i.e., /categories/dotnet would become /tags/dotnet.html.
I created a JSON file with all the redirects I needed based on the route syntax in Azure Static Web Apps configuration file:
[
{
"route": "^/categories/(.*)$",
"redirect": "/tags/$1.html"
}
]
I import the JSON file in my Azure Function source file and check if any of the routes match the URL:
import { parseURL } from "ufo";
import routes from "./redirects.json";
const parsedURL = parseURL(originalUrl);
const matchedRoute = routes.find((route) =>
new RegExp(route.route).test(parsedURL.pathname),
);
If there's a match, I invoke the replace method and return the result as a permanent redirect:
if (matchedRoute) {
const redirectTarget = parsedURL.pathname.replace(
new RegExp(matchedRoute.route),
matchedRoute.redirect,
);
return {
status: 301, // permanent redirect
headers: { location: redirectTarget },
};
}
If there's no match, I return a custom 404 page. I have a special error page in my blog for this purpose which I load from the Azure Function:
const notFoundUrl = `${parsedURL.protocol}//${parsedURL.host}/errors/404.html`;
const notFoundPageText = await (await fetch(notFoundUrl)).text();
return {
status: 404,
headers: {
"Content-Type": "text/html",
},
body: notFoundPageText,
};
You can find a sample project in my GitHub repository. It contains a minimal static web site and a fully working redirect logic for the example described above. It's a simplified version of the one I'm currently using for my blog.
The repository also includes a GitHub Actions workflow to deploy the sample site and the function to Azure Static Web Apps. Of course, you'll have to create your own Azure Static Web App in order to do that, and store the deployment token for it in the AZURE_STATIC_WEB_APPS_API_TOKEN secret in GitHub Actions.
Using an Azure Function as navigation fallback works really well for my redirect needs. I'm glad to have learned about this possibility from a random blog post since this option doesn't seem to be mentioned anywhere in the documentation.
