Skip to content

Commit 33a43e1

Browse files
aster-voidclaude
andcommitted
modules/site: add programmatic URL redirects with DB lookup
Redirect legacy article URLs to new YYYY-MM-DD-slug format: - Format 2: /articles/YYYY/MM-DD_slug -> /articles/YYYY-MM-DD-slug - Format 1: /articles/{old-slug} -> DB lookup -> /articles/{new-slug} 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 640b520 commit 33a43e1

File tree

1 file changed

+30
-6
lines changed

1 file changed

+30
-6
lines changed

src/hooks.server.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ import type { Handle } from "@sveltejs/kit";
22
import { redirect } from "@sveltejs/kit";
33
import { sequence } from "@sveltejs/kit/hooks";
44
import { svelteKitHandler } from "better-auth/svelte-kit";
5+
import { like } from "drizzle-orm";
56
import { building } from "$app/environment";
67
import { auth } from "$lib/server/drivers/auth";
8+
import { db } from "$lib/server/drivers/db";
9+
import { article } from "$lib/shared/models/schema";
710

811
const handleAuth: Handle = async ({ event, resolve }) => {
912
return await svelteKitHandler({ event, resolve, auth, building });
@@ -12,12 +15,33 @@ const handleAuth: Handle = async ({ event, resolve }) => {
1215
const handleRedirect: Handle = async ({ event, resolve }) => {
1316
const path = event.url.pathname;
1417

15-
// Redirect old article URL structure: /articles/YYYY/MM-DD_slug -> /articles/MM-DD_slug
16-
const oldArticlePattern = /^\/articles\/(\d{4})\/(.+)$/;
17-
const match = path.match(oldArticlePattern);
18-
if (match) {
19-
const slug = match[2];
20-
redirect(301, `/articles/${slug}`);
18+
// Format 2: /articles/YYYY/MM-DD_slug -> /articles/YYYY-MM-DD-slug
19+
const format2Pattern = /^\/articles\/(\d{4})\/(\d{2}-\d{2})_(.+?)\/?$/;
20+
const format2Match = path.match(format2Pattern);
21+
if (format2Match) {
22+
const [, year, monthDay, slug] = format2Match;
23+
redirect(301, `/articles/${year}-${monthDay}-${slug}`);
24+
}
25+
26+
// Format 1: /articles/{old-slug} -> DB lookup -> /articles/{new-slug}
27+
// Old slugs don't have YYYY-MM-DD prefix, new slugs do
28+
const format1Pattern = /^\/articles\/([^/]+?)\/?$/;
29+
const format1Match = path.match(format1Pattern);
30+
if (format1Match?.[1]) {
31+
const oldSlug = format1Match[1];
32+
// Skip if already in new format (YYYY-MM-DD-slug)
33+
if (/^\d{4}-\d{2}-\d{2}-/.test(oldSlug)) {
34+
return resolve(event);
35+
}
36+
// DB lookup: find article where slug ends with the old slug pattern
37+
const found = await db
38+
.select({ slug: article.slug })
39+
.from(article)
40+
.where(like(article.slug, `%-${oldSlug}`))
41+
.limit(1);
42+
if (found[0]) {
43+
redirect(301, `/articles/${found[0].slug}`);
44+
}
2145
}
2246

2347
return resolve(event);

0 commit comments

Comments
 (0)