Skip to content

Commit ff402ae

Browse files
committed
Add trailing slashes for internal link redirect fix
1 parent 7fcbefb commit ff402ae

File tree

6 files changed

+64
-19
lines changed

6 files changed

+64
-19
lines changed

astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
// https://astro.build/config
1717
export default defineConfig({
1818
site: 'https://shovanch.com',
19+
trailingSlash: 'always',
1920
build: {
2021
format: 'directory',
2122
// Inline all styles to eliminate render-blocking CSS

src/components/base-head.astro

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ const canonicalURL = canonical
205205
"prerender": [
206206
{
207207
"where": { "href_matches": "/*" },
208-
"eagerness": "moderate"
208+
"eagerness": "eager"
209209
}
210210
]
211211
}
@@ -220,19 +220,27 @@ const canonicalURL = canonical
220220
<script>
221221
// Only track page views when page is actually visible (not prerendered)
222222
if (document.prerendering) {
223-
document.addEventListener('prerenderingchange', () => {
224-
if (window.umami) umami.track();
225-
}, { once: true });
223+
document.addEventListener(
224+
'prerenderingchange',
225+
() => {
226+
if (window.umami) umami.track();
227+
},
228+
{ once: true },
229+
);
226230
} else if (document.visibilityState === 'visible') {
227231
document.addEventListener('DOMContentLoaded', () => {
228232
if (window.umami) umami.track();
229233
});
230234
} else {
231-
document.addEventListener('visibilitychange', () => {
232-
if (document.visibilityState === 'visible' && window.umami) {
233-
umami.track();
234-
}
235-
}, { once: true });
235+
document.addEventListener(
236+
'visibilitychange',
237+
() => {
238+
if (document.visibilityState === 'visible' && window.umami) {
239+
umami.track();
240+
}
241+
},
242+
{ once: true },
243+
);
236244
}
237245
</script>
238246

src/components/nav-desktop.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { menuItems } from '~/config/site';
2121
</div>
2222
<div class="flex items-center gap-3" role="group" aria-label="Search options">
2323
<a
24-
href="/search"
24+
href="/search/"
2525
class="focus:ring-theme-text px-2 py-1 font-sans text-xl font-medium"
2626
aria-label="Search posts and pages"
2727
>

src/config/plugins.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { rehypeExternalLinks } from '../libs/rehype-external-links';
1212
import { rehypeImageCaptions } from '../libs/rehype-image-captions';
1313
import { rehypeRawHtmlInCode } from '../libs/rehype-raw-html-in-code';
1414
import { rehypeToc } from '../libs/rehype-toc';
15+
import { rehypeTrailingSlash } from '../libs/rehype-trailing-slash';
1516
import { remarkCallouts } from '../libs/remark-callouts';
1617
import { remarkSidenotes } from '../libs/remark-sidenotes';
1718
import { remarkWikilinksSimple } from '../libs/remark-wikilinks-simple.js';
@@ -25,6 +26,7 @@ export const remarkPlugins = [
2526
];
2627

2728
export const rehypePlugins: any[] = [
29+
rehypeTrailingSlash,
2830
rehypeToc,
2931
rehypeHeadingIds,
3032
[

src/config/site.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ export const defaultMeta: SiteMeta = {
5050
export const navigationConfig = {
5151
mainMenu: [
5252
{ label: 'home', url: '/' },
53-
{ label: 'posts', url: '/posts' },
54-
{ label: 'notes', url: '/notes' },
55-
{ label: 'fragments', url: '/fragments' },
56-
{ label: 'about', url: '/about' },
57-
{ label: 'search', url: '/search' },
53+
{ label: 'posts', url: '/posts/' },
54+
{ label: 'notes', url: '/notes/' },
55+
{ label: 'fragments', url: '/fragments/' },
56+
{ label: 'about', url: '/about/' },
57+
{ label: 'search', url: '/search/' },
5858
],
5959
socialLinks: [
6060
{ label: 'email', url: `mailto:${siteConfig.email}` },
@@ -87,19 +87,19 @@ export const exploreLinks: ExploreLink[] = [
8787
{
8888
title: 'Posts',
8989
description: "Essays I've taken time to finish",
90-
url: '/posts',
90+
url: '/posts/',
9191
},
9292
{
9393
title: 'Notes',
9494
description: "Stuff I'm learning, mostly for myself",
95-
url: '/notes',
95+
url: '/notes/',
9696
},
9797
{
9898
title: 'Fragments',
9999
description: 'Stray thoughts, links, tiny updates',
100-
url: '/fragments',
100+
url: '/fragments/',
101101
},
102-
{ title: 'Search', description: 'Everything Ive written', url: '/search' },
102+
{ title: 'Search', description: "Everything I've written", url: '/search/' },
103103
];
104104

105105
// Re-export for backward compatibility (from the old data/index.ts)

src/libs/rehype-trailing-slash.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Rehype plugin to add trailing slashes to internal links
3+
* Ensures consistency with Astro's trailingSlash: 'always' config
4+
*/
5+
import type { Root } from 'hast';
6+
import { visit } from 'unist-util-visit';
7+
8+
export function rehypeTrailingSlash() {
9+
return (tree: Root) => {
10+
visit(tree, 'element', (node) => {
11+
if (node.tagName !== 'a') return;
12+
13+
const href = node.properties?.href;
14+
if (typeof href !== 'string') return;
15+
16+
// Skip external links, anchors, mailto, tel, and already-slashed paths
17+
if (
18+
href.startsWith('http') ||
19+
href.startsWith('#') ||
20+
href.startsWith('mailto:') ||
21+
href.startsWith('tel:') ||
22+
href.endsWith('/') ||
23+
href.includes('.') // Skip files like /file.pdf, /image.png
24+
) {
25+
return;
26+
}
27+
28+
// Add trailing slash to internal links
29+
// Handle links with anchors: /about#section -> /about/#section
30+
const [path, hash] = href.split('#');
31+
node.properties.href = hash ? `${path}/#${hash}` : `${path}/`;
32+
});
33+
};
34+
}

0 commit comments

Comments
 (0)