diff --git a/docs/functions/clean-html-urls.ts b/docs/functions/clean-html-urls.ts new file mode 100644 index 0000000000..eadd654a68 --- /dev/null +++ b/docs/functions/clean-html-urls.ts @@ -0,0 +1,27 @@ +// Redirect legacy *.html URLs to their extensionless equivalents. Runs as an +// edge function because a wildcard _redirects rule can't force a redirect +// away from a file that physically exists at the "from" path. +import { Context } from "@netlify/edge-functions"; + +const excluded: Set = new Set([ + "/netlify-forms.html", + "/docs/kubernetes-admission-control.html", +]); + +export default async ( + request: Request, + context: Context, +): Promise => { + const url: URL = new URL(request.url); + const pathname: string = url.pathname; + + if (!pathname.endsWith(".html") || excluded.has(pathname)) { + return undefined; + } + + url.pathname = pathname === "/index.html" + ? "/" + : pathname.slice(0, -".html".length); + + return Response.redirect(url.toString(), 301); +}; diff --git a/docs/src/pages/ecosystem/index.jsx b/docs/src/pages/ecosystem/index.jsx index 13422f43db..0722a67bf7 100644 --- a/docs/src/pages/ecosystem/index.jsx +++ b/docs/src/pages/ecosystem/index.jsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import Link from "@docusaurus/Link"; +import useBaseUrl from "@docusaurus/useBaseUrl"; import Heading from "@theme/Heading"; import Layout from "@theme/Layout"; @@ -94,7 +95,7 @@ const EcosystemIndex = (props) => { return ( { diff --git a/netlify.toml b/netlify.toml index 7d1974c0f0..e76b4ff3a0 100644 --- a/netlify.toml +++ b/netlify.toml @@ -27,6 +27,11 @@ function = "badge" path = "/docs/*" function = "version-redirect" +# Canonicalize legacy .html URLs onto their clean-path equivalents. +[[edge_functions]] +path = "/*" +function = "clean-html-urls" + # function to process form notifications from netlify forms and send them to slack [[edge_functions]] path = "/api/feedback"