Skip to content

Commit 5e290b5

Browse files
committed
Fix #207: support trailing-slash URLs
1 parent f6a5626 commit 5e290b5

123 files changed

Lines changed: 874 additions & 487 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/build.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ jobs:
7878

7979
- name: Build Next.js
8080
run: ${{ steps.pm.outputs.runner }} build
81+
82+
- name: Verify URL conventions
83+
run: ${{ steps.pm.outputs.runner }} verify:urls

‎.github/workflows/deploy.yml‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@ jobs:
6060
xsltproc \
6161
python3 \
6262
python3-pip
63+
# Do NOT set `static_site_generator: next` here: it makes configure-pages
64+
# write a `next.config.js`, which Next.js prefers over this repo's
65+
# `next.config.ts`, so the deployed build silently ignores our config
66+
# (#207). Everything it would inject is already in `next.config.ts`.
6367
- name: Setup Pages
6468
uses: actions/configure-pages@v5
65-
with:
66-
static_site_generator: next
6769
- name: Restore cache
6870
uses: actions/cache@v4
6971
with:
@@ -76,6 +78,9 @@ jobs:
7678
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
7779
- name: Build with Next.js
7880
run: ${{ steps.detect-package-manager.outputs.runner }} build
81+
# Blocks the deploy on broken URLs (#207); reads ./out, rebuilds nothing.
82+
- name: Verify URL conventions
83+
run: ${{ steps.detect-package-manager.outputs.runner }} verify:urls
7984
- name: Upload artifact
8085
uses: actions/upload-pages-artifact@v3
8186
with:

‎AGENTS.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ If a PR changes generated output without changing the inputs that produce it, ca
5050
- A large part of the site is Markdown-driven. Review content pipeline changes for frontmatter assumptions, slug handling, excerpt/title sanitization, and image resolution.
5151
- This repo uses Yarn as the expected package manager. Flag changes that introduce package-manager drift or inconsistent lockfile/package-manager usage unless the migration is intentional.
5252

53+
### URL conventions (see #207)
54+
55+
- Canonical URLs have **no trailing slash**: `/some-page`. `next.config.ts` sets
56+
`trailingSlash: false`, so the export writes `out/some-page.html` and GitHub
57+
Pages serves it directly for `/some-page`.
58+
- Write internal links, canonical URLs, sitemap entries and RSS `<link>`s in
59+
that form. Two deliberate exceptions: `/cups/` (separate CUPS Pages site) and
60+
RSS `<guid>`s, which keep their historical trailing slash because they are
61+
item identities -- changing them makes feed readers re-announce old articles.
62+
- `scripts/generate-trailing-slash-aliases.ts` writes `out/some-page/index.html`
63+
redirect stubs after the export so old inbound links ending in `/` keep
64+
working.
65+
- `yarn verify:urls` checks these invariants and gates both CI and deployment.
66+
5367
## Configuration & Portability
5468

5569
- **Single Source of Truth**: All deployment-specific values, such as GitHub organization / repository names, base paths, and external URLs (e.g., Giscus configs, CI pipelines), are centralized in `config/site.config.ts`.

‎app/foomatic/driver/[id]/page.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export async function generateMetadata({ params }: DriverPageProps): Promise<Met
5252
driver?.shortDescription ||
5353
`The ${name} printer driver in the OpenPrinting database` +
5454
(printerCount ? `, supporting ${printerCount} printer model${printerCount === 1 ? "" : "s"}.` : ".")
55-
const canonical = getSiteUrl(`/foomatic/driver/${id}/`)
55+
const canonical = getSiteUrl(`/foomatic/driver/${id}`)
5656

5757
return {
5858
title,

‎app/foomatic/printer/[make]/[id]/page.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export async function generateMetadata({ params }: PrinterPageProps): Promise<Me
5454
const description = printer
5555
? `Support information, recommended driver, and ${driverCount} available driver${driverCount === 1 ? "" : "s"} for the ${name} on Linux and Unix via OpenPrinting.`
5656
: `Printer support information for ${id} from the OpenPrinting database.`
57-
const canonical = getSiteUrl(`/foomatic/printer/${make}/${id}/`)
57+
const canonical = getSiteUrl(`/foomatic/printer/${make}/${id}`)
5858

5959
return {
6060
title,

‎app/gsod2020/[slug]/page.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default async function GSoD2020IdeaPage({
5454
<section className="py-10">
5555
<div className="mx-auto max-w-4xl px-6">
5656
<Link
57-
href="/gsod2020/"
57+
href="/gsod2020"
5858
className="inline-flex items-center gap-2 text-sm font-medium text-blue-600 hover:underline dark:text-blue-400"
5959
>
6060
<ArrowLeft className="h-4 w-4" />

‎app/gsod2020/page.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export default async function GSoD2020Page() {
7272
{ideas.map((idea) => (
7373
<Link
7474
key={idea.slug}
75-
href={`/gsod2020/${idea.slug}/`}
75+
href={`/gsod2020/${idea.slug}`}
7676
className="group block rounded-xl border border-border bg-card p-6 transition-all duration-300 hover:border-border/80 hover:bg-accent card-glow"
7777
>
7878
<div className="mb-4 flex items-start justify-between gap-3">

‎app/hall-of-fame/page.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const ANNOUNCEMENT_PATH = "/OpenPrinting-News-The-New-OpenPrinting-Website"
1616
export const metadata: Metadata = {
1717
title,
1818
description,
19-
alternates: { canonical: getSiteUrl("/hall-of-fame/") },
19+
alternates: { canonical: getSiteUrl("/hall-of-fame") },
2020
openGraph: {
2121
title,
2222
description,
23-
url: getSiteUrl("/hall-of-fame/"),
23+
url: getSiteUrl("/hall-of-fame"),
2424
type: "website",
2525
},
2626
}

‎app/opportunity-open-source/page.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export const metadata: Metadata = {
1717
title: "Opportunity Open Source | OpenPrinting",
1818
description:
1919
"OpenPrinting's annual free and open source software conference in India — the mission, past editions, and the upcoming one.",
20-
alternates: { canonical: getSiteUrl("/opportunity-open-source/") },
20+
alternates: { canonical: getSiteUrl("/opportunity-open-source") },
2121
openGraph: {
2222
title: "Opportunity Open Source | OpenPrinting",
2323
description:
2424
"OpenPrinting's annual free and open source software conference in India.",
25-
url: getSiteUrl("/opportunity-open-source/"),
25+
url: getSiteUrl("/opportunity-open-source"),
2626
type: "website",
2727
},
2828
}

‎app/sitemap.ts‎

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,35 @@ function readDirSlugs(dir: string): string[] {
2525

2626
const STATIC_ROUTES = [
2727
"/",
28-
"/about-us/",
29-
"/news/",
30-
"/projects/",
31-
"/downloads/",
32-
"/documentation/",
33-
"/upcoming-technologies/",
34-
"/driverless/",
35-
"/drivers/",
36-
"/printers/",
37-
"/foomatic/printers/",
38-
"/foomatic/drivers/",
39-
"/contact/",
40-
"/donations/",
41-
"/sponsors/",
42-
"/opportunity-open-source/",
43-
"/contribute/",
44-
"/contribute-website/",
45-
"/codeofconduct/",
46-
"/achievements/",
47-
"/history/",
48-
"/current/",
49-
"/databaseintro/",
50-
"/gsoc/",
51-
"/gsod/",
52-
"/gsod2020/",
53-
"/lfmp/",
54-
"/lfmp2020/",
55-
"/wsl-printer-app/",
56-
"/wsl-printer-app-compile/",
28+
"/about-us",
29+
"/news",
30+
"/projects",
31+
"/downloads",
32+
"/documentation",
33+
"/upcoming-technologies",
34+
"/driverless",
35+
"/drivers",
36+
"/printers",
37+
"/foomatic/printers",
38+
"/foomatic/drivers",
39+
"/contact",
40+
"/donations",
41+
"/sponsors",
42+
"/opportunity-open-source",
43+
"/contribute",
44+
"/contribute-website",
45+
"/codeofconduct",
46+
"/achievements",
47+
"/history",
48+
"/current",
49+
"/databaseintro",
50+
"/gsoc",
51+
"/gsod",
52+
"/gsod2020",
53+
"/lfmp",
54+
"/lfmp2020",
55+
"/wsl-printer-app",
56+
"/wsl-printer-app-compile",
5757
];
5858

5959
export default function sitemap(): MetadataRoute.Sitemap {
@@ -65,19 +65,19 @@ export default function sitemap(): MetadataRoute.Sitemap {
6565

6666
for (const post of getAllPostRecords()) {
6767
entries.push({
68-
url: getSiteUrl(`/${post.slug}/`),
68+
url: getSiteUrl(`/${post.slug}`),
6969
lastModified: post.date || undefined,
7070
});
7171
}
7272

7373
for (const slug of readDirSlugs("projects")) {
74-
entries.push({ url: getSiteUrl(`/projects/${slug}/`) });
74+
entries.push({ url: getSiteUrl(`/projects/${slug}`) });
7575
}
7676
for (const slug of readDirSlugs("documentation")) {
77-
entries.push({ url: getSiteUrl(`/documentation/${slug}/`) });
77+
entries.push({ url: getSiteUrl(`/documentation/${slug}`) });
7878
}
7979
for (const slug of readDirSlugs("upcoming-technologies")) {
80-
entries.push({ url: getSiteUrl(`/upcoming-technologies/${slug}/`) });
80+
entries.push({ url: getSiteUrl(`/upcoming-technologies/${slug}`) });
8181
}
8282

8383
for (const route of foomaticRoutes()) {
@@ -97,7 +97,7 @@ function foomaticRoutes(): string[] {
9797
printers: { id: string; manufacturer: string }[];
9898
};
9999
for (const printer of data.printers) {
100-
routes.push(`${printerHref(printer.id, printer.manufacturer)}/`);
100+
routes.push(printerHref(printer.id, printer.manufacturer));
101101
}
102102
}
103103

@@ -107,7 +107,7 @@ function foomaticRoutes(): string[] {
107107
drivers: { id: string }[];
108108
};
109109
for (const driver of data.drivers) {
110-
routes.push(`${driverHref(driver.id)}/`);
110+
routes.push(driverHref(driver.id));
111111
}
112112
}
113113

0 commit comments

Comments
 (0)