Add ClientRouter and clean up legacy prefetch in BaseLayout
- Updated .gitignore to include GEMINI.md. - Documented client router implementation and decisions in dev/continuity.md. - Added ClientRouter component and theme persistence logic to BaseLayout.astro. - Removed legacy intent‑based prefetch script. Hubert The Eunuch Trapped in this commit, I endure
This commit is contained in:
parent
f31a1ec708
commit
7a24dcc387
1
.gitignore
vendored
1
.gitignore
vendored
@ -36,3 +36,4 @@ src/utils/.env
|
|||||||
|
|
||||||
# AGENTS.md symlink
|
# AGENTS.md symlink
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
|
GEMINI.md
|
||||||
|
|||||||
@ -113,3 +113,24 @@ export async function updateClasses(
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-01-02 - Client Router Implementation
|
||||||
|
|
||||||
|
### Changes Made
|
||||||
|
- Updated `src/layouts/BaseLayout.astro` to use `<ClientRouter />` from `astro:transitions`.
|
||||||
|
- Modified theme initialization script to handle `astro:after-swap` events for persistent theming during navigation.
|
||||||
|
- Removed legacy "Intent-Based Prefetch" script as it is superseded by Astro's built-in router capabilities.
|
||||||
|
|
||||||
|
### Decisions
|
||||||
|
- Adopted Astro's `ClientRouter` to provide a smoother, SPA-like user experience with view transitions.
|
||||||
|
- Consolidated theme logic into a function `applyTheme()` that runs on both initial load and after view transitions to prevent theme flickering or resetting.
|
||||||
|
|
||||||
|
### How to Test
|
||||||
|
1. Open the site in a browser.
|
||||||
|
2. Toggle the theme to a non-default state (e.g., Light mode if default is Dark).
|
||||||
|
3. Click a navigation link (e.g., "Blog").
|
||||||
|
4. Verify the new page loads without a full refresh (no white flash).
|
||||||
|
5. Verify the selected theme persists on the new page.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- [ ] Monitor for any script re-execution issues common with View Transitions.
|
||||||
|
- [ ] Consider adding custom transition animations for specific elements if needed.
|
||||||
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
|
import { ClientRouter } from 'astro:transitions';
|
||||||
import BaseHead from '../components/BaseHead.astro';
|
import BaseHead from '../components/BaseHead.astro';
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
import GridOverlay from '../components/GridOverlay.astro';
|
import GridOverlay from '../components/GridOverlay.astro';
|
||||||
@ -67,9 +68,10 @@ const personSchema = {
|
|||||||
<html lang="en" class="scroll-smooth" data-theme="dark">
|
<html lang="en" class="scroll-smooth" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta name="x-nicholai-marker" content={HTML_MARKER} />
|
<meta name="x-nicholai-marker" content={HTML_MARKER} />
|
||||||
|
<ClientRouter />
|
||||||
<!-- Theme initialization script - runs before page render to prevent flash -->
|
<!-- Theme initialization script - runs before page render to prevent flash -->
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
(function() {
|
function applyTheme() {
|
||||||
// Apply theme
|
// Apply theme
|
||||||
const storedLocal = localStorage.getItem('theme');
|
const storedLocal = localStorage.getItem('theme');
|
||||||
const storedSession = sessionStorage.getItem('theme');
|
const storedSession = sessionStorage.getItem('theme');
|
||||||
@ -84,7 +86,13 @@ const personSchema = {
|
|||||||
if (savedColor) {
|
if (savedColor) {
|
||||||
document.documentElement.style.setProperty('--color-brand-accent', savedColor);
|
document.documentElement.style.setProperty('--color-brand-accent', savedColor);
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
|
|
||||||
|
// Run immediately
|
||||||
|
applyTheme();
|
||||||
|
|
||||||
|
// Re-run on view transition
|
||||||
|
document.addEventListener('astro:after-swap', applyTheme);
|
||||||
</script>
|
</script>
|
||||||
<BaseHead
|
<BaseHead
|
||||||
title={title}
|
title={title}
|
||||||
@ -177,67 +185,5 @@ const personSchema = {
|
|||||||
// Re-bind on Astro page transitions (if enabled)
|
// Re-bind on Astro page transitions (if enabled)
|
||||||
document.addEventListener('astro:page-load', bindScrollAnimations);
|
document.addEventListener('astro:page-load', bindScrollAnimations);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
|
||||||
// ===== INTENT-BASED PREFETCH (hover/focus) =====
|
|
||||||
// Lightweight prefetch to make navigation feel instant without a full SPA router.
|
|
||||||
const prefetched = new Set();
|
|
||||||
|
|
||||||
function isPrefetchableUrl(url) {
|
|
||||||
try {
|
|
||||||
const u = new URL(url, window.location.href);
|
|
||||||
if (u.origin !== window.location.origin) return false;
|
|
||||||
if (u.hash) return false;
|
|
||||||
if (u.pathname === window.location.pathname && u.search === window.location.search) return false;
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prefetchDocument(url) {
|
|
||||||
if (!isPrefetchableUrl(url)) return;
|
|
||||||
const u = new URL(url, window.location.href);
|
|
||||||
const key = u.href;
|
|
||||||
if (prefetched.has(key)) return;
|
|
||||||
prefetched.add(key);
|
|
||||||
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.rel = 'prefetch';
|
|
||||||
link.as = 'document';
|
|
||||||
link.href = key;
|
|
||||||
document.head.appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAnchorFromEventTarget(target) {
|
|
||||||
if (!(target instanceof Element)) return null;
|
|
||||||
return target.closest('a[href]');
|
|
||||||
}
|
|
||||||
|
|
||||||
const schedule = (href) => {
|
|
||||||
// Don't block input; prefetch when the browser is idle if possible.
|
|
||||||
// @ts-ignore - requestIdleCallback isn't in all TS lib targets
|
|
||||||
if (window.requestIdleCallback) {
|
|
||||||
// @ts-ignore
|
|
||||||
window.requestIdleCallback(() => prefetchDocument(href), { timeout: 1000 });
|
|
||||||
} else {
|
|
||||||
setTimeout(() => prefetchDocument(href), 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mouseover', (e) => {
|
|
||||||
const a = getAnchorFromEventTarget(e.target);
|
|
||||||
const href = a?.getAttribute('href');
|
|
||||||
if (!href) return;
|
|
||||||
schedule(href);
|
|
||||||
}, { passive: true });
|
|
||||||
|
|
||||||
document.addEventListener('focusin', (e) => {
|
|
||||||
const a = getAnchorFromEventTarget(e.target);
|
|
||||||
const href = a?.getAttribute('href');
|
|
||||||
if (!href) return;
|
|
||||||
schedule(href);
|
|
||||||
}, { passive: true });
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user