refactor: remove guide route prefix (#11146)

* refactor: remove guide route prefix

* chore: implement backwards compat redirect

* Change guide redirect destination and permanence

Updated the redirect destination and permanence for the guide route.

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Almeida
2025-10-07 10:50:53 +01:00
committed by GitHub
parent f109fc9b42
commit 7de5b4a349
9 changed files with 66 additions and 60 deletions

View File

@@ -1,9 +1,9 @@
{ {
"pages": [ "pages": [
"[MessageCircleQuestion][FAQ](/guide/legacy/popular-topics/faq)", "[MessageCircleQuestion][FAQ](/legacy/popular-topics/faq)",
"[ArrowDownToLine][Updating to v14](/guide/legacy/additional-info/changes-in-v14)", "[ArrowDownToLine][Updating to v14](/legacy/additional-info/changes-in-v14)",
"[LibraryBig][Documentation](https://discord.js.org/docs)", "[LibraryBig][Documentation](https://discord.js.org/docs)",
"[Info][Introduction](/guide/legacy)", "[Info][Introduction](/legacy)",
"---Setup---", "---Setup---",
"preparations", "preparations",
"---Your App---", "---Your App---",

View File

@@ -8,7 +8,7 @@ export async function generateStaticParams() {
return source.generateParams(); return source.generateParams();
} }
export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) { export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }): Promise<Metadata> {
const params = await props.params; const params = await props.params;
const page = source.getPage(params.slug); const page = source.getPage(params.slug);
@@ -16,7 +16,7 @@ export async function generateMetadata(props: { params: Promise<{ slug?: string[
notFound(); notFound();
} }
const image = ['/docs-og', ...(params.slug ?? []), 'image.png'].join('/'); const image = ['/og', ...(params.slug ?? []), 'image.png'].join('/');
return { return {
title: page.data.title, title: page.data.title,
description: page.data.description, description: page.data.description,
@@ -27,7 +27,7 @@ export async function generateMetadata(props: { params: Promise<{ slug?: string[
card: 'summary_large_image', card: 'summary_large_image',
images: image, images: image,
}, },
} satisfies Metadata; };
} }
export default async function Page(props: { readonly params: Promise<{ slug?: string[] }> }) { export default async function Page(props: { readonly params: Promise<{ slug?: string[] }> }) {

View File

@@ -1,42 +0,0 @@
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import type { CSSProperties, ReactNode } from 'react';
import { baseOptions } from '@/app/layout.config';
import { source } from '@/lib/source';
export default function Layout({ children }: { readonly children: ReactNode }) {
return (
<DocsLayout
sidebar={{
tabs: {
transform(option, node) {
const meta = source.getNodeMeta(node);
if (!meta || !node.icon) return option;
// category selection color based on path src/styles/base.css
const color = `var(--${meta.path.split('/')[0]}-color, var(--color-fd-foreground))`;
return {
...option,
icon: (
<div
className="size-full rounded-lg text-(--tab-color) max-md:border max-md:bg-(--tab-color)/10 max-md:p-1.5 [&_svg]:size-full"
style={
{
'--tab-color': color,
} as CSSProperties
}
>
{node.icon}
</div>
),
};
},
},
}}
tree={source.pageTree}
{...baseOptions}
>
{children}
</DocsLayout>
);
}

View File

@@ -1,11 +1,14 @@
import { Analytics } from '@vercel/analytics/react'; import { Analytics } from '@vercel/analytics/react';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { RootProvider } from 'fumadocs-ui/provider'; import { RootProvider } from 'fumadocs-ui/provider';
import { GeistMono } from 'geist/font/mono'; import { GeistMono } from 'geist/font/mono';
import { GeistSans } from 'geist/font/sans'; import { GeistSans } from 'geist/font/sans';
import type { Metadata, Viewport } from 'next'; import type { Metadata, Viewport } from 'next';
import type { PropsWithChildren } from 'react'; import type { CSSProperties, PropsWithChildren } from 'react';
import { Body } from '@/app/layout.client'; import { Body } from '@/app/layout.client';
import { source } from '@/lib/source';
import { ENV } from '@/util/env'; import { ENV } from '@/util/env';
import { baseOptions } from './layout.config';
import '@/styles/base.css'; import '@/styles/base.css';
@@ -18,7 +21,7 @@ export const viewport: Viewport = {
}; };
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL(ENV.IS_LOCAL_DEV ? `http://localhost:${ENV.PORT}` : 'https://next.discordjs.guide'), metadataBase: new URL(ENV.IS_LOCAL_DEV ? `http://localhost:${ENV.PORT}` : 'https://discordjs.guide'),
title: { title: {
template: '%s | discord.js', template: '%s | discord.js',
default: 'discord.js', default: 'discord.js',
@@ -74,7 +77,41 @@ export default async function RootLayout({ children }: PropsWithChildren) {
return ( return (
<html className={`${GeistSans.variable} ${GeistMono.variable} antialiased`} lang="en" suppressHydrationWarning> <html className={`${GeistSans.variable} ${GeistMono.variable} antialiased`} lang="en" suppressHydrationWarning>
<Body> <Body>
<RootProvider>{children}</RootProvider> <RootProvider>
<DocsLayout
sidebar={{
tabs: {
transform(option, node) {
const meta = source.getNodeMeta(node);
if (!meta || !node.icon) return option;
// category selection color based on path src/styles/base.css
const color = `var(--${meta.path.split('/')[0]}-color, var(--color-fd-foreground))`;
return {
...option,
icon: (
<div
className="size-full rounded-lg text-(--tab-color) max-md:border max-md:bg-(--tab-color)/10 max-md:p-1.5 [&_svg]:size-full"
style={
{
'--tab-color': color,
} as CSSProperties
}
>
{node.icon}
</div>
),
};
},
},
}}
tree={source.pageTree}
{...baseOptions}
>
{children}
</DocsLayout>
</RootProvider>
<Analytics /> <Analytics />
</Body> </Body>
</html> </html>

View File

@@ -12,7 +12,7 @@ export function generateStaticParams() {
export async function GET(_req: Request, { params }: { params: Promise<{ slug: string[] }> }) { export async function GET(_req: Request, { params }: { params: Promise<{ slug: string[] }> }) {
const { slug } = await params; const { slug } = await params;
const page = source.getPage(slug.slice(0, -1)); const page = source.getPage(slug.slice(0, -1));
// const fontData = await fetch(new URL('../../../assets/Geist-Regular.ttf', import.meta.url), { // const fontData = await fetch(new URL('../../assets/Geist-Regular.ttf', import.meta.url), {
// next: { revalidate: 604_800 }, // next: { revalidate: 604_800 },
// }).then(async (res) => res.arrayBuffer()); // }).then(async (res) => res.arrayBuffer());

View File

@@ -1,5 +0,0 @@
import { redirect } from 'next/navigation';
export default async function Page() {
redirect('/guide/legacy');
}

View File

@@ -15,6 +15,6 @@ export const source = loader({
return undefined; return undefined;
}, },
baseUrl: '/guide/', baseUrl: '/',
source: docs.toFumadocsSource(), source: docs.toFumadocsSource(),
}); });

View File

@@ -0,0 +1,16 @@
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// TODO: Remove this eventually
if (request.nextUrl.pathname.startsWith('/guide/')) {
const newUrl = request.nextUrl.clone();
newUrl.pathname = newUrl.pathname.replace('/guide/', '/');
return NextResponse.redirect(new URL(newUrl.pathname, request.url));
}
return NextResponse.redirect(new URL('/legacy', request.url));
}
export const config = {
matcher: ['/', '/guide/:path*'],
};

View File

@@ -39,8 +39,8 @@ export default {
}, },
{ {
source: '/guide/:path*', source: '/guide/:path*',
destination: 'https://next.discordjs.guide/guide/:path*', destination: 'https://discordjs.guide/:path*',
permanent: true, permanent: false,
}, },
]; ];
}, },