Skip to content

Commit 9b48430

Browse files
JacobCoffeeclaude
andcommitted
fix all internal links to use base path for GitHub Pages
Add withBase() helper that prepends import.meta.env.BASE_URL to all hardcoded href paths across 17 files. This ensures all navigation, pagination, tag/author/blog links work correctly when deployed to the /python-insider-blog/ subdirectory on GitHub Pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 790166e commit 9b48430

File tree

17 files changed

+84
-57
lines changed

17 files changed

+84
-57
lines changed

src/components/BaseHead.astro

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ interface Props {
55
image?: string;
66
}
77
8+
import { withBase } from "../lib/utils";
89
const { title, description = "The official blog of the Python core development team.", image } = Astro.props;
910
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
1011
---
1112

1213
<meta charset="utf-8" />
1314
<meta name="viewport" content="width=device-width, initial-scale=1" />
14-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
15-
<link rel="sitemap" href="/sitemap-index.xml" />
16-
<link rel="alternate" type="application/rss+xml" title="Python Insider" href="/rss.xml" />
15+
<link rel="icon" type="image/svg+xml" href={withBase("/favicon.svg")} />
16+
<link rel="sitemap" href={withBase("/sitemap-index.xml")} />
17+
<link rel="alternate" type="application/rss+xml" title="Python Insider" href={withBase("/rss.xml")} />
1718
<link rel="canonical" href={canonicalURL} />
1819

1920
<!-- Fonts: Plus Jakarta Sans (display) + Inter (body) + JetBrains Mono (code) -->

src/components/BlogPostCard.astro

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
import { formatDate, slugify } from "../lib/utils";
2+
import { formatDate, slugify, withBase } from "../lib/utils";
33
44
interface Props {
55
slug: string;
@@ -18,13 +18,13 @@ const { slug, title, publishDate, author, description, tags, showEditLink = true
1818
<article class="post-card group relative px-5 py-4">
1919
<div class="flex items-start justify-between gap-4">
2020
<div class="min-w-0 flex-1">
21-
<a href={`/blog/${slug}`} class="block">
21+
<a href={withBase(`/blog/${slug}`)} class="block">
2222
<h3 class="text-base font-semibold leading-snug text-zinc-900 transition-colors group-hover:text-[#306998] dark:text-zinc-100 dark:group-hover:text-[#ffd43b]" style="font-family: var(--font-display);">
2323
{title}
2424
</h3>
2525
</a>
2626
<div class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-zinc-500 dark:text-zinc-400">
27-
<a href={`/authors/${slugify(author)}`} class="font-medium text-zinc-600 hover:text-[#306998] dark:text-zinc-300 dark:hover:text-[#ffd43b] transition-colors">{author}</a>
27+
<a href={withBase(`/authors/${slugify(author)}`)} class="font-medium text-zinc-600 hover:text-[#306998] dark:text-zinc-300 dark:hover:text-[#ffd43b] transition-colors">{author}</a>
2828
<span class="text-zinc-300 dark:text-zinc-600">&middot;</span>
2929
<time datetime={publishDate}>{formatDate(publishDate)}</time>
3030
</div>
@@ -35,7 +35,7 @@ const { slug, title, publishDate, author, description, tags, showEditLink = true
3535
<div class="mt-2.5 flex flex-wrap gap-1.5">
3636
{tags.slice(0, 4).map((tag) => (
3737
<a
38-
href={`/tags/${tag}`}
38+
href={withBase(`/tags/${tag}`)}
3939
class="tag-pill rounded-md bg-zinc-100 px-2 py-0.5 text-xs font-medium text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400"
4040
>
4141
{tag}
@@ -47,7 +47,7 @@ const { slug, title, publishDate, author, description, tags, showEditLink = true
4747

4848
{isDev && showEditLink && (
4949
<a
50-
href={`/keystatic/collection/posts/item/${slug}`}
50+
href={withBase(`/keystatic/collection/posts/item/${slug}`)}
5151
class="edit-btn mt-1 flex-shrink-0 rounded-md p-1.5 text-zinc-300 hover:bg-zinc-100 hover:text-zinc-500 dark:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-400"
5252
title="Edit in Keystatic"
5353
>

src/components/CommandPalette.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,20 @@ function formatDate(iso: string): string {
5858
});
5959
}
6060

61+
/* ── Base path helper ──────────────────────────────── */
62+
63+
const BASE = import.meta.env.BASE_URL.replace(/\/$/, "");
64+
function withBase(path: string): string {
65+
return `${BASE}${path.startsWith("/") ? path : `/${path}`}`;
66+
}
67+
6168
/* ── Navigation items with chord shortcuts ──────────── */
6269

6370
const NAV_ITEMS = [
64-
{ label: "Go to Home", href: "/", icon: HomeIcon, chord: "H" },
65-
{ label: "Go to Blog", href: "/blog", icon: BlogIcon, chord: "B" },
66-
{ label: "Go to Tags", href: "/tags", icon: TagIcon, chord: "T" },
67-
{ label: "Go to RSS Feed", href: "/rss.xml", icon: RssIcon, chord: "R" },
71+
{ label: "Go to Home", href: withBase("/"), icon: HomeIcon, chord: "H" },
72+
{ label: "Go to Blog", href: withBase("/blog"), icon: BlogIcon, chord: "B" },
73+
{ label: "Go to Tags", href: withBase("/tags"), icon: TagIcon, chord: "T" },
74+
{ label: "Go to RSS Feed", href: withBase("/rss.xml"), icon: RssIcon, chord: "R" },
6875
] as const;
6976

7077
/* ── Component ──────────────────────────────────────── */
@@ -285,7 +292,7 @@ export default function CommandPalette({ open: controlledOpen, onOpenChange }: P
285292
<Command.Item
286293
key={post.id}
287294
value={`post-${post.id}`}
288-
onSelect={() => navigate(`/blog/${post.id}`)}
295+
onSelect={() => navigate(withBase(`/blog/${post.id}`))}
289296
>
290297
<div className="cmdk-post-icon" aria-hidden="true">
291298
<ArticleIcon />

src/components/Footer.astro

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import PythonLogo from "./PythonLogo.astro";
3+
import { withBase } from "../lib/utils";
34
const isDev = import.meta.env.DEV;
45
const year = new Date().getFullYear();
56
---
@@ -9,7 +10,7 @@ const year = new Date().getFullYear();
910
<div class="grid grid-cols-1 gap-8 sm:grid-cols-3">
1011
<!-- Brand column -->
1112
<div>
12-
<a href="/" class="flex items-center gap-2">
13+
<a href={withBase("/")} class="flex items-center gap-2">
1314
<PythonLogo width={24} height={24} />
1415
<span class="text-sm font-semibold text-zinc-800 dark:text-zinc-200" style="font-family: var(--font-display);">Python Insider</span>
1516
</a>
@@ -34,15 +35,15 @@ const year = new Date().getFullYear();
3435
<h3 class="text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400">Subscribe</h3>
3536
<ul class="mt-3 space-y-2">
3637
<li>
37-
<a href="/rss.xml" class="inline-flex items-center gap-1.5 text-sm text-zinc-600 transition-colors hover:text-orange-600 dark:text-zinc-300 dark:hover:text-orange-400">
38+
<a href={withBase("/rss.xml")} class="inline-flex items-center gap-1.5 text-sm text-zinc-600 transition-colors hover:text-orange-600 dark:text-zinc-300 dark:hover:text-orange-400">
3839
<svg class="h-3.5 w-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M6.18 15.64a2.18 2.18 0 012.18 2.18C8.36 19 7.38 20 6.18 20 5 20 4 19 4 17.82a2.18 2.18 0 012.18-2.18zM4 4.44A15.56 15.56 0 0119.56 20h-2.83A12.73 12.73 0 004 7.27V4.44zM4 10.1a9.9 9.9 0 019.9 9.9h-2.83A7.07 7.07 0 004 12.93V10.1z"/></svg>
3940
RSS Feed
4041
</a>
4142
</li>
42-
<li><a href="/tags" class="text-sm text-zinc-600 transition-colors hover:text-[#306998] dark:text-zinc-300 dark:hover:text-[#ffd43b]">Browse by Tag</a></li>
43+
<li><a href={withBase("/tags")} class="text-sm text-zinc-600 transition-colors hover:text-[#306998] dark:text-zinc-300 dark:hover:text-[#ffd43b]">Browse by Tag</a></li>
4344
{isDev && (
4445
<li>
45-
<a href="/keystatic" class="inline-flex items-center gap-1.5 text-sm text-zinc-600 transition-colors hover:text-amber-600 dark:text-zinc-300 dark:hover:text-amber-400">
46+
<a href={withBase("/keystatic")} class="inline-flex items-center gap-1.5 text-sm text-zinc-600 transition-colors hover:text-amber-600 dark:text-zinc-300 dark:hover:text-amber-400">
4647
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
4748
Keystatic CMS
4849
</a>

src/components/Header.astro

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
---
22
import PythonLogo from "./PythonLogo.astro";
33
import CommandPalette from "./CommandPalette";
4+
import { withBase } from "../lib/utils";
45
const isDev = import.meta.env.DEV;
56
const navItems = [
6-
{ label: "Home", href: "/" },
7-
{ label: "Blog", href: "/blog" },
7+
{ label: "Home", href: withBase("/") },
8+
{ label: "Blog", href: withBase("/blog") },
89
];
910
const currentPath = Astro.url.pathname;
1011
---
1112

1213
<header class="sticky top-0 z-50 border-b border-zinc-200 bg-white/80 backdrop-blur-xl dark:border-zinc-800 dark:bg-[#0f1117]/80">
1314
<nav class="mx-auto flex max-w-6xl items-center justify-between px-6 py-3">
14-
<a href="/" class="group flex items-center gap-2.5">
15+
<a href={withBase("/")} class="group flex items-center gap-2.5">
1516
<PythonLogo width={30} height={30} class="transition-transform duration-300 group-hover:scale-110" />
1617
<span class="text-lg font-semibold tracking-tight text-zinc-900 dark:text-zinc-100" style="font-family: var(--font-display);">
1718
Python Insider
@@ -35,7 +36,7 @@ const currentPath = Astro.url.pathname;
3536

3637
{isDev && (
3738
<a
38-
href="/keystatic"
39+
href={withBase("/keystatic")}
3940
class="ml-1 flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-medium text-zinc-500 transition-all hover:bg-amber-50 hover:text-amber-700 dark:text-zinc-400 dark:hover:bg-amber-900/20 dark:hover:text-amber-400"
4041
title="Keystatic CMS"
4142
>
@@ -63,7 +64,7 @@ const currentPath = Astro.url.pathname;
6364
</button>
6465

6566
<a
66-
href="/rss.xml"
67+
href={withBase("/rss.xml")}
6768
class="rounded-lg p-2 text-zinc-500 transition-colors hover:bg-orange-50 hover:text-orange-600 dark:text-zinc-400 dark:hover:bg-orange-900/20 dark:hover:text-orange-400"
6869
title="RSS Feed"
6970
>

src/layouts/BlogPostLayout.astro

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
import BaseLayout from "./BaseLayout.astro";
3-
import { formatDate, slugify } from "../lib/utils";
3+
import { formatDate, slugify, withBase } from "../lib/utils";
44
55
interface Props {
66
title: string;
@@ -27,7 +27,7 @@ const { title, publishDate, updatedDate, author, description, tags, slug } = Ast
2727
</div>
2828
{isDev && slug && (
2929
<a
30-
href={`/keystatic/collection/posts/item/${slug}`}
30+
href={withBase(`/keystatic/collection/posts/item/${slug}`)}
3131
class="edit-btn-always mt-2 flex-shrink-0 rounded-lg bg-zinc-100 p-2.5 text-zinc-400 hover:bg-amber-100 hover:text-amber-700 dark:bg-zinc-800 dark:text-zinc-600 dark:hover:bg-amber-900/30 dark:hover:text-amber-400"
3232
title="Edit in Keystatic"
3333
>
@@ -39,7 +39,7 @@ const { title, publishDate, updatedDate, author, description, tags, slug } = Ast
3939
</div>
4040

4141
<div class="mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-zinc-500 dark:text-zinc-400">
42-
<a href={`/authors/${slugify(author)}`} class="font-medium text-zinc-800 hover:text-[#306998] dark:text-zinc-200 dark:hover:text-[#ffd43b] transition-colors">{author}</a>
42+
<a href={withBase(`/authors/${slugify(author)}`)} class="font-medium text-zinc-800 hover:text-[#306998] dark:text-zinc-200 dark:hover:text-[#ffd43b] transition-colors">{author}</a>
4343
<span class="text-zinc-300 dark:text-zinc-600">/</span>
4444
<time datetime={publishDate}>{formatDate(publishDate)}</time>
4545
{updatedDate && (
@@ -54,7 +54,7 @@ const { title, publishDate, updatedDate, author, description, tags, slug } = Ast
5454
<div class="mt-5 flex flex-wrap gap-1.5">
5555
{tags.map((tag) => (
5656
<a
57-
href={`/tags/${tag}`}
57+
href={withBase(`/tags/${tag}`)}
5858
class="tag-pill rounded-md bg-zinc-100 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:bg-zinc-700/60 dark:text-zinc-300"
5959
>
6060
{tag}
@@ -73,15 +73,15 @@ const { title, publishDate, updatedDate, author, description, tags, slug } = Ast
7373
<footer class="not-prose mt-14 border-t border-zinc-200 pt-8 dark:border-zinc-700">
7474
<div class="flex items-center justify-between">
7575
<a
76-
href="/blog"
76+
href={withBase("/blog")}
7777
class="group inline-flex items-center gap-2 text-sm font-medium text-[#306998] transition-colors hover:text-[#254f73] dark:text-[#ffd43b] dark:hover:text-[#f0c419]"
7878
>
7979
<span class="transition-transform group-hover:-translate-x-0.5">&larr;</span>
8080
Back to all posts
8181
</a>
8282
{isDev && slug && (
8383
<a
84-
href={`/keystatic/collection/posts/item/${slug}`}
84+
href={withBase(`/keystatic/collection/posts/item/${slug}`)}
8585
class="inline-flex items-center gap-1.5 rounded-lg bg-zinc-100 px-3 py-1.5 text-xs font-medium text-zinc-500 transition-colors hover:bg-amber-100 hover:text-amber-700 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-amber-900/30 dark:hover:text-amber-400"
8686
>
8787
<svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">

src/lib/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ export function formatDateShort(date: string): string {
1414
});
1515
}
1616

17+
/**
18+
* Prepends the configured Astro base path to an absolute path.
19+
* Dev: "/" + "/blog" → "/blog"
20+
* Prod: "/python-insider-blog/" + "/blog" → "/python-insider-blog/blog"
21+
*/
22+
export function withBase(path: string): string {
23+
const base = import.meta.env.BASE_URL.replace(/\/$/, "");
24+
return `${base}${path.startsWith("/") ? path : `/${path}`}`;
25+
}
26+
1727
export function slugify(text: string): string {
1828
return text
1929
.normalize("NFD")

src/pages/404.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const prerender = true;
33
44
import BaseLayout from "../layouts/BaseLayout.astro";
55
import PythonLogo from "../components/PythonLogo.astro";
6+
import { withBase } from "../lib/utils";
67
---
78

89
<BaseLayout title="Not Found | Python Insider">
@@ -22,13 +23,13 @@ import PythonLogo from "../components/PythonLogo.astro";
2223
</p>
2324
<div class="mt-8 flex items-center gap-3">
2425
<a
25-
href="/"
26+
href={withBase("/")}
2627
class="inline-flex items-center gap-2 rounded-lg bg-[#306998] px-5 py-2.5 text-sm font-medium text-white shadow-md transition-all hover:bg-[#254f73] hover:shadow-lg dark:bg-[#ffd43b] dark:text-zinc-900 dark:hover:bg-[#f0c419]"
2728
>
2829
Go home
2930
</a>
3031
<a
31-
href="/blog"
32+
href={withBase("/blog")}
3233
class="inline-flex items-center gap-2 rounded-lg border border-zinc-300 px-5 py-2.5 text-sm font-medium text-zinc-700 transition-all hover:border-zinc-400 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-300 dark:hover:border-zinc-500 dark:hover:bg-zinc-800"
3334
>
3435
Browse posts

src/pages/authors/[author].astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { CollectionEntry } from "astro:content";
44
import BaseLayout from "../../layouts/BaseLayout.astro";
55
import BlogPostCard from "../../components/BlogPostCard.astro";
66
import { getCollection } from "astro:content";
7-
import { slugify } from "../../lib/utils";
7+
import { slugify, withBase } from "../../lib/utils";
88
99
export const prerender = true;
1010
@@ -48,7 +48,7 @@ const avatarUrl = author.avatar || (author.github ? `https://github.com/${author
4848
<BaseLayout title={`${author.name} | Python Insider`}>
4949
<div class="animate-fade-up">
5050
<a
51-
href="/authors"
51+
href={withBase("/authors")}
5252
class="group inline-flex items-center gap-1.5 text-sm font-medium text-[#306998] transition-colors hover:text-[#254f73] dark:text-[#ffd43b] dark:hover:text-[#f0c419]"
5353
>
5454
<span class="transition-transform group-hover:-translate-x-0.5">&larr;</span>
@@ -74,7 +74,7 @@ const avatarUrl = author.avatar || (author.github ? `https://github.com/${author
7474
</h1>
7575
{isDev && (
7676
<a
77-
href={`/keystatic/collection/authors/item/${slug}`}
77+
href={withBase(`/keystatic/collection/authors/item/${slug}`)}
7878
class="edit-btn-always mt-1 flex-shrink-0 rounded-lg bg-zinc-100 p-2 text-zinc-400 hover:bg-amber-100 hover:text-amber-700 dark:bg-zinc-800 dark:text-zinc-600 dark:hover:bg-amber-900/30 dark:hover:text-amber-400"
7979
title="Edit in Keystatic"
8080
>

src/pages/authors/index.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export const prerender = true;
33
44
import BaseLayout from "../../layouts/BaseLayout.astro";
55
import { getCollection } from "astro:content";
6-
import { slugify } from "../../lib/utils";
6+
import { slugify, withBase } from "../../lib/utils";
77
88
const allPosts = await getCollection("posts");
99
const publishedPosts = allPosts.filter((p) => p.data.published);
@@ -46,7 +46,7 @@ const maxCount = authorsWithCounts[0]?.count ?? 1;
4646
const pct = Math.round((author.count / maxCount) * 100);
4747
return (
4848
<a
49-
href={`/authors/${author.slug}`}
49+
href={withBase(`/authors/${author.slug}`)}
5050
class="group flex items-center gap-4 rounded-lg px-4 py-3 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800/60"
5151
>
5252
{author.github ? (

0 commit comments

Comments
 (0)