feat(site): migrate from Next.js to Astro
Build and Deploy / deploy (push) Successful in 1m42s

Replace Next.js 16 + React 19 with Astro 5. Same visual design,
same deploy pipeline, zero client-side framework.

- All components rewritten as .astro files
- Dark mode via inline scripts (no React context)
- Timeline animation via IntersectionObserver script
- Nav active state computed at build time
- Self-hosted Source Code Pro woff2 fonts
- Drop Font Awesome (icons were never loaded)
- Drop unused headshot PNG (1MB, unreferenced)
- Fix pfSense hardware refs (Netgate 1100, not N100)
- Output: 212KB static HTML vs 2.6MB before
- JS shipped: ~700 bytes inline vs ~130KB React runtime
This commit is contained in:
2026-05-18 20:07:24 -04:00
parent d34f9f136c
commit 0c5d9e03b1
47 changed files with 4898 additions and 6345 deletions
+73
View File
@@ -0,0 +1,73 @@
---
const pathname = Astro.url.pathname;
const links = [
{ href: "/", label: "tyler" },
{ href: "/homelab/", label: "homelab" },
{ href: "/projects/", label: "projects" },
];
---
<header class="sticky top-0 z-50 bg-[var(--color-surface)] border-b border-[var(--color-border)]">
<nav class="max-w-[740px] mx-auto px-4ch h-11 flex items-center justify-between">
<a
href="/"
class="font-mono text-sm font-bold text-[var(--color-text)] hover:text-[var(--color-text-label)]"
>
~/
</a>
<ul class="flex items-center gap-2ch">
{links.map(({ href, label }) => {
const active = pathname === href || pathname === href.replace(/\/$/, "");
return (
<li>
<a
href={href}
aria-current={active ? "page" : undefined}
class:list={[
"font-mono text-sm",
active
? "text-[var(--color-text)]"
: "text-[var(--color-text-label)] hover:text-[var(--color-text)]",
]}
>
{label}
</a>
</li>
);
})}
<li>
<button
data-theme-toggle
aria-label="Switch to light mode"
class="font-mono text-sm text-[var(--color-text-label)] hover:text-[var(--color-text)] cursor-pointer"
>
[light]
</button>
</li>
</ul>
</nav>
</header>
<script>
const btn = document.querySelector("[data-theme-toggle]") as HTMLButtonElement;
function update() {
const isDark = document.documentElement.classList.contains("dark");
btn.textContent = isDark ? "[light]" : "[dark]";
btn.setAttribute(
"aria-label",
isDark ? "Switch to light mode" : "Switch to dark mode",
);
}
btn.addEventListener("click", () => {
const next = !document.documentElement.classList.contains("dark");
document.documentElement.classList.toggle("dark", next);
localStorage.setItem("lerko96-dark-mode", String(next));
update();
});
update();
</script>