docs: 301 retired /ag-ui mirror paths to docs.ag-ui.com

#7092 deleted showcase/shell-docs/src/content/ag-ui/ and its route. The 96
mirror URLs still in the production sitemap carry search ranking; without
redirects they become 404s once the site rebuilds.

87 of the 96 slugs exist unchanged on docs.ag-ui.com, so a wildcard carries
them. The nine that do not are listed explicitly ahead of the wildcard so a
reader never lands on an upstream 404.
This commit is contained in:
Jordan Ritter
2026-09-13 09:55:46 -07:00
parent abaec72f36
commit af65ba2dee
2 changed files with 171 additions and 0 deletions
+88
View File
@@ -294,6 +294,91 @@ const CHANNEL_REDIRECTS: PermanentRedirect[] = [
...CHANNEL_ROOT_REDIRECTS,
];
// ----------------------------------------------------------------
// Retired AG-UI mirror (`/ag-ui/*`).
//
// The mirrored AG-UI docs tree was deleted in #7092; AG-UI documentation is
// canonical at docs.ag-ui.com. 96 `/ag-ui/*` URLs were in the production
// sitemap and carry accumulated search ranking, so they are handed to the
// upstream site with a 301 rather than left to 404.
//
// The mirror's slugs match upstream 1:1 for 87 of the 96 paths (verified by
// HTTP probe against docs.ag-ui.com), so a single wildcard carries the bulk
// of the tree. The nine paths with no upstream page are listed explicitly
// ABOVE the wildcard so the specific rule wins.
//
// Suffix handling: a raw Markdown request reaches redirects before the
// `.md` / `.mdx` rewrite, and upstream serves `.md` but NOT `.mdx`
// (docs.ag-ui.com/concepts/agents.md -> 200, .../concepts/agents.mdx -> 404),
// so both suffixes are collapsed onto upstream `.md`.
// ----------------------------------------------------------------
const AG_UI_DOCS_ORIGIN = "https://docs.ag-ui.com";
function agUiMirrorRedirects(
mirrorPath: string,
upstreamPath: string,
): PermanentRedirect[] {
return [
{
source: `/ag-ui${mirrorPath}.mdx`,
destination: `${AG_UI_DOCS_ORIGIN}${upstreamPath}.md`,
permanent: true,
},
{
source: `/ag-ui${mirrorPath}.md`,
destination: `${AG_UI_DOCS_ORIGIN}${upstreamPath}.md`,
permanent: true,
},
{
source: `/ag-ui${mirrorPath}`,
destination: `${AG_UI_DOCS_ORIGIN}${upstreamPath}`,
permanent: true,
},
];
}
// Retired mirror paths whose slug does NOT exist upstream. Each destination
// was probed and returns 200; the bare wildcard below would send these to an
// upstream 404, which is worse for readers than the page simply being gone.
const AG_UI_MIRROR_EXCEPTIONS = [
// Both drafts graduated into the Concepts section upstream.
["/drafts/interrupts", "/concepts/interrupts"],
["/drafts/multimodal-messages", "/concepts/messages"],
// The Dart SDK kept every page except its client overview.
["/sdk/dart/client/overview", "/sdk/dart/overview"],
// Upstream reduced the Rust SDK to a single overview page.
["/sdk/rust/client/agent-trait", "/sdk/rust/overview"],
["/sdk/rust/client/http-agent", "/sdk/rust/overview"],
["/sdk/rust/client/subscriber", "/sdk/rust/overview"],
["/sdk/rust/core/events", "/sdk/rust/overview"],
["/sdk/rust/core/overview", "/sdk/rust/overview"],
["/sdk/rust/core/types", "/sdk/rust/overview"],
] as const;
const AG_UI_MIRROR_REDIRECTS: PermanentRedirect[] = [
...AG_UI_MIRROR_EXCEPTIONS.flatMap(([mirrorPath, upstreamPath]) =>
agUiMirrorRedirects(mirrorPath, upstreamPath),
),
// The mirror root rendered the AG-UI introduction page.
...agUiMirrorRedirects("", "/introduction"),
// Everything else keeps its slug upstream.
{
source: "/ag-ui/:path*.mdx",
destination: `${AG_UI_DOCS_ORIGIN}/:path*.md`,
permanent: true,
},
{
source: "/ag-ui/:path*.md",
destination: `${AG_UI_DOCS_ORIGIN}/:path*.md`,
permanent: true,
},
{
source: "/ag-ui/:path*",
destination: `${AG_UI_DOCS_ORIGIN}/:path*`,
permanent: true,
},
];
// NEXT_PUBLIC_BASE_URL and NEXT_PUBLIC_SHELL_URL are read at REQUEST
// time by the server `getRuntimeConfig()` reader and injected into the
// client via `window.__SHOWCASE_CONFIG__` from the root layout. They
@@ -375,6 +460,9 @@ const nextConfig: NextConfig = {
// OSS-615: legacy global, scoped, generated-reference, and Bots URLs
// resolve directly to the canonical Slack/Teams guide trees.
...CHANNEL_REDIRECTS,
// OSS: the retired `/ag-ui/*` mirror hands its search equity to the
// canonical upstream docs at docs.ag-ui.com (see AG_UI_MIRROR_REDIRECTS).
...AG_UI_MIRROR_REDIRECTS,
{
// Built-in agent is the default framework, so its overview page
// is the docs root. Avoid surfacing a redundant "Introduction"
@@ -56,4 +56,87 @@ describe("next.config redirects", () => {
]),
);
});
it("hands the retired /ag-ui mirror to docs.ag-ui.com", async () => {
vi.stubEnv("NEXT_PUBLIC_BASE_URL", "http://localhost:3003");
vi.stubEnv("NEXT_PUBLIC_SHELL_URL", "http://localhost:3000");
const nextConfig = (await import("../../../next.config")).default;
const redirects = (await nextConfig.redirects?.()) ?? [];
expect(redirects).toEqual(
expect.arrayContaining([
// Bulk of the mirror keeps its slug upstream.
{
source: "/ag-ui/:path*",
destination: "https://docs.ag-ui.com/:path*",
permanent: true,
},
// Upstream serves `.md` but not `.mdx`, so both collapse onto `.md`.
{
source: "/ag-ui/:path*.md",
destination: "https://docs.ag-ui.com/:path*.md",
permanent: true,
},
{
source: "/ag-ui/:path*.mdx",
destination: "https://docs.ag-ui.com/:path*.md",
permanent: true,
},
// Mirror root rendered the upstream introduction page.
{
source: "/ag-ui",
destination: "https://docs.ag-ui.com/introduction",
permanent: true,
},
// Paths with no upstream equivalent land on the nearest live page.
{
source: "/ag-ui/drafts/interrupts",
destination: "https://docs.ag-ui.com/concepts/interrupts",
permanent: true,
},
{
source: "/ag-ui/drafts/multimodal-messages",
destination: "https://docs.ag-ui.com/concepts/messages",
permanent: true,
},
{
source: "/ag-ui/sdk/dart/client/overview",
destination: "https://docs.ag-ui.com/sdk/dart/overview",
permanent: true,
},
{
source: "/ag-ui/sdk/rust/core/types",
destination: "https://docs.ag-ui.com/sdk/rust/overview",
permanent: true,
},
]),
);
});
it("orders every /ag-ui exception ahead of the catch-all", async () => {
vi.stubEnv("NEXT_PUBLIC_BASE_URL", "http://localhost:3003");
vi.stubEnv("NEXT_PUBLIC_SHELL_URL", "http://localhost:3000");
const nextConfig = (await import("../../../next.config")).default;
const redirects = (await nextConfig.redirects?.()) ?? [];
const catchAll = redirects.findIndex(
(redirect) => redirect.source === "/ag-ui/:path*",
);
expect(catchAll).toBeGreaterThan(-1);
const exceptions = redirects.filter(
(redirect) =>
typeof redirect.destination === "string" &&
redirect.destination.startsWith("https://docs.ag-ui.com") &&
!redirect.source.includes(":path*"),
);
// 9 retired paths with no upstream equivalent + the mirror root, each
// emitted bare, `.md` and `.mdx`.
expect(exceptions).toHaveLength(30);
for (const exception of exceptions) {
expect(redirects.indexOf(exception)).toBeLessThan(catchAll);
}
});
});