Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/(bench)/bench/[...notFound]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

import type { Metadata } from 'next';
import { notFound } from 'next/navigation';

import { NOT_FOUND_METADATA } from '@/bench/lib/not-found-metadata';

// The 404's title after hydration too, not the layout's (see NOT_FOUND_METADATA).
export const metadata: Metadata = NOT_FOUND_METADATA;

/**
* Every bench URL no other bench route matches. The app has two root layouts,
* and an unmatched URL gets the marketing site's 404 (src/app/global-not-found.tsx).
Expand Down
12 changes: 8 additions & 4 deletions src/app/(bench)/bench/chart/[slug]/page.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ vi.mock('@/bench/lib/queries', () => ({
vi.mock('@/bench/components/Chart', () => ({ Chart: () => null }));

const { generateMetadata } = await import('@/app/(bench)/bench/chart/[slug]/page');
const { NOT_FOUND_METADATA } = await import('@/bench/lib/not-found-metadata');

type Query = Record<string, string | string[] | undefined>;

Expand All @@ -84,6 +85,9 @@ async function metadata(slug: string, query: Query = {}): Promise<Redirect | obj
}

const permanent = (to: string) => new Redirect('permanent', to);
// The fake has no chart, so a request that is not redirected gets the 404's
// metadata, as the page 404s.
const served = NOT_FOUND_METADATA;
const temporary = (to: string) => new Redirect('temporary', to);

afterEach(() => {
Expand All @@ -93,13 +97,13 @@ afterEach(() => {

describe('the chart page redirect', () => {
it('serves the default window as sent, whatever else the query holds', async () => {
expect(await metadata('ok', { utm_source: 'x' })).toEqual({});
expect(await metadata('ok', { utm_source: 'x' })).toEqual(served);
expect(cachedDefaultChartPayload).toHaveBeenCalledWith('ok');
expect(cachedFilterUniverse).not.toHaveBeenCalled();
});

it('serves a canonical non-default window without a redirect', async () => {
expect(await metadata('ok', { n: 'all', engine: 'duckdb' })).toEqual({});
expect(await metadata('ok', { n: 'all', engine: 'duckdb' })).toEqual(served);
expect(chartPayload).toHaveBeenCalledWith(expect.anything(), { kind: 'all' });
});

Expand Down Expand Up @@ -137,12 +141,12 @@ describe('the chart page redirect', () => {
});

it('treats a repeated parameter as absent, as the page does', async () => {
expect(await metadata('ok', { n: ['all', '100'] })).toEqual({});
expect(await metadata('ok', { n: ['all', '100'] })).toEqual(served);
expect(cachedDefaultChartPayload).toHaveBeenCalledWith('ok');
});

it('leaves a malformed slug to the 404, without a redirect', async () => {
expect(await metadata('bad', { n: 'all' })).toEqual({});
expect(await metadata('bad', { n: 'all' })).toEqual(served);
expect(chartPayload).not.toHaveBeenCalled();
expect(cachedDefaultChartPayload).not.toHaveBeenCalled();
});
Expand Down
4 changes: 3 additions & 1 deletion src/app/(bench)/bench/chart/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
singleSearchParam,
} from '@/bench/lib/chart-format';
import { cachedDefaultChartPayload, cachedFilterUniverse } from '@/bench/lib/data-cache';
import { NOT_FOUND_METADATA } from '@/bench/lib/not-found-metadata';
import { chartPayload, type ChartResponse } from '@/bench/lib/queries';
import { chartDescription, chartUrl, openGraph } from '@/bench/lib/site-files';
import { chartKeyFromSlug, chartKeyToSlug, type ChartKey } from '@/bench/lib/slug';
Expand Down Expand Up @@ -131,7 +132,8 @@ export async function generateMetadata({
await redirectToCanonical(slug, sp);
const chart = await getChart(slug, singleSearchParam(sp.n));
if (chart === null) {
return {};
// The page 404s: keep the 404's title after hydration (see NOT_FOUND_METADATA).
return NOT_FOUND_METADATA;
}
const key = chartKeyFromSlug(slug);
const title = `${chart.display_name} | Vortex`;
Expand Down
13 changes: 2 additions & 11 deletions src/app/(bench)/bench/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,10 @@
import type { Metadata } from 'next';

import { BenchHeader } from '@/bench/components/BenchHeader';
import { NOT_FOUND_METADATA } from '@/bench/lib/not-found-metadata';
import { NotFoundStatus } from '@/components/not-found';

const TITLE = 'Page not found | Vortex';
const SENTENCE = 'There is no page at this address.';

// The site's 404 metadata (src/app/(site)/not-found.tsx). The Open Graph
// object replaces the layout's, so the page has no og:url: a missing page is
// not bench's home.
export const metadata: Metadata = {
title: TITLE,
description: SENTENCE,
openGraph: { title: TITLE, description: SENTENCE, siteName: 'Vortex', type: 'website' },
};
export const metadata: Metadata = NOT_FOUND_METADATA;

/**
* bench's 404, for the paths the catch-all route hands to `notFound()`: the
Expand Down
23 changes: 23 additions & 0 deletions src/bench/lib/not-found-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

import type { Metadata } from 'next';

const TITLE = 'Page not found | Vortex';
const SENTENCE = 'There is no page at this address.';

/**
* bench's 404 metadata, the site's (src/app/(site)/not-found.tsx). The Open
* Graph object replaces the layout's, so the page has no og:url: a missing
* page is not bench's home.
*
* not-found.tsx exports it, and so does every route that calls `notFound()`.
* The server renders not-found.tsx's metadata, but after hydration the client
* sets the route's own, so a route without it showed the layout's title,
* "Benchmarks | Vortex", on its 404.
*/
export const NOT_FOUND_METADATA: Metadata = {
title: TITLE,
description: SENTENCE,
openGraph: { title: TITLE, description: SENTENCE, siteName: 'Vortex', type: 'website' },
};
16 changes: 16 additions & 0 deletions tests/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,22 @@ test.describe("404", () => {
page.getByRole("heading", { level: 1, name: "404" })
).toBeVisible();
});

// 7 Nov 2026: the server sent the 404's title, and then the client set the
// route's own title, which was bench's layout's "Benchmarks | Vortex". Both
// routes that call notFound() must give the 404's title after hydration.
for (const path of ["/bench/no-such-page", "/bench/chart/not-a-chart"]) {
test(`${path} keeps the 404 title after it loads`, async ({ page }) => {
const res = await page.goto(path);
expect(res?.status()).toBe(404);
await page.waitForLoadState("networkidle");
await expect(page).toHaveTitle("Page not found | Vortex");
// Hydration has run when the theme bootstrap's class is gone.
await expect(page.locator("html")).toHaveClass(/\bjs\b/);
await page.waitForTimeout(1000);
await expect(page).toHaveTitle("Page not found | Vortex");
});
}
});

// Note 56: under the blog a 404 follows the reader's theme, and the mark
Expand Down
Loading