Replace Astro/Strapi site with Next.js static export

- Fresh structure: Next.js App Router + Tailwind v4 at the repo root, exported
  as static files (output: export) and served by static-web-server
- Remove the Strapi CMS, Astro app, Pagefind search, comments, old scripts,
  Lighthouse config and compose files; content in content/ is now plain
  markdown with YAML frontmatter
- Pages: About, Writing, Reading (3D bookshelf) and Portfolio, styled after
  adammaj.com
- SEO: metadata and canonicals, JSON-LD, generated OG images, sitemap, robots,
  RSS and JSON feeds, llms.txt
- Umami via PUBLIC_UMAMI_SCRIPT_URL / PUBLIC_UMAMI_WEBSITE_ID (build args)
- Dockerfile builds the export and serves it with sws.toml (security headers,
  clean URLs, .well-known, OG image content type, immutable asset caching)
- Prettier and cspell for content with a husky/lint-staged pre-commit hook
- Old redirect list dropped to start from a clean slate
This commit is contained in:
2026-09-30 12:48:14 +03:00
parent cafcb5b4e0
commit ec6401ac38
227 changed files with 4357 additions and 27144 deletions
+90
View File
@@ -0,0 +1,90 @@
import type { Metadata, Viewport } from "next";
import { Lora } from "next/font/google";
import Script from "next/script";
import { JsonLd } from "@/components/JsonLd";
import { Layout } from "@/components/Layout";
import { site } from "@/lib/site";
import "./globals.css";
import "katex/dist/katex.min.css";
const lora = Lora({
variable: "--font-lora",
subsets: ["latin"],
display: "swap",
});
export const viewport: Viewport = { themeColor: "#ffffff" };
export const metadata: Metadata = {
metadataBase: new URL(site.baseUrl),
title: { default: site.title, template: `%s | ${site.name}` },
description: site.description,
keywords: site.keywords,
authors: [{ name: site.name, url: site.baseUrl }],
creator: site.name,
publisher: site.name,
openGraph: {
siteName: site.name,
type: "website",
locale: site.locale,
url: "/",
title: site.title,
description: site.description,
},
twitter: {
card: "summary_large_image",
site: site.twitter,
creator: site.twitter,
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-image-preview": "large",
"max-snippet": -1,
},
},
};
const websiteLd = {
"@context": "https://schema.org",
"@type": "WebSite",
name: site.name,
alternateName: site.tagline,
url: site.baseUrl,
inLanguage: "en",
author: { "@type": "Person", name: site.name },
};
const umamiScript = process.env.PUBLIC_UMAMI_SCRIPT_URL;
const umamiId = process.env.PUBLIC_UMAMI_WEBSITE_ID;
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html lang="en" className={lora.variable}>
<head>
<link
rel="alternate"
type="application/rss+xml"
title={`${site.name} RSS`}
href="/rss.xml"
/>
<link
rel="alternate"
type="application/feed+json"
title={`${site.name} JSON Feed`}
href="/feed.json"
/>
</head>
<body>
<JsonLd data={websiteLd} />
<Layout>{children}</Layout>
{umamiScript && umamiId && (
<Script defer src={umamiScript} data-website-id={umamiId} />
)}
</body>
</html>
);
}