Files
andwati-nextgen/src/app/reading/[slug]/page.tsx
T
andwati 63007a1c09 Rebuild site in Tailwind to match adammaj.com layout with andwati content
- Port adammaj.com layout, sidebar and 3D bookshelf from Chakra to Tailwind v4,
  matched to measured pixel values
- Pages: About, Writing, Reading (bookshelf), Portfolio; content exported to
  markdown with YAML frontmatter, KaTeX math, callouts and YouTube embeds
- Shiki (github-light) code highlighting via rehype-pretty-code
- SEO: per-page metadata and canonicals, JSON-LD, generated OG images,
  sitemap, robots, RSS/JSON feeds, llms.txt
- Umami analytics via PUBLIC_UMAMI_SCRIPT_URL / PUBLIC_UMAMI_WEBSITE_ID
- Prettier + cspell for content, husky/lint-staged pre-commit hook
- Multi-stage Dockerfile (Node 24, pnpm, standalone output) for Dokploy
- Remove unused Chakra, emotion, framer-motion, posthog, next-seo and MDX deps
2026-09-30 12:38:09 +03:00

96 lines
2.7 KiB
TypeScript

import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { breadcrumbs, JsonLd } from "@/components/JsonLd";
import { Mdx } from "@/components/Mdx";
import { byline } from "@/lib/books";
import { getBooks } from "@/lib/content";
import { abs, site } from "@/lib/site";
export function generateStaticParams() {
return getBooks().map((b) => ({ slug: b.slug }));
}
export async function generateMetadata({
params,
}: PageProps<"/reading/[slug]">): Promise<Metadata> {
const { slug } = await params;
const book = getBooks().find((b) => b.slug === slug);
if (!book) return {};
const url = `/reading/${slug}`;
const description = byline(book.data) || site.description;
return {
title: book.data.title,
description,
keywords: book.data.tags,
alternates: { canonical: url },
openGraph: {
type: "article",
url,
title: book.data.title,
description,
siteName: site.name,
locale: site.locale,
},
twitter: {
card: "summary_large_image",
title: book.data.title,
description,
},
};
}
export default async function Book({ params }: PageProps<"/reading/[slug]">) {
const { slug } = await params;
const book = getBooks().find((b) => b.slug === slug);
if (!book) notFound();
const url = abs(`/reading/${slug}`);
const item = {
"@type": book.data.kind === "paper" ? "ScholarlyArticle" : "Book",
name: book.data.title,
url: book.data.url,
author: book.data.authors?.map((name) => ({ "@type": "Person", name })),
};
const reviewLd = book.data.rating
? {
"@context": "https://schema.org",
"@type": "Review",
url,
name: `Review: ${book.data.title}`,
datePublished: book.data.dateFinished,
author: { "@type": "Person", name: site.name, url: site.baseUrl },
itemReviewed: item,
reviewRating: {
"@type": "Rating",
ratingValue: book.data.rating,
bestRating: 5,
worstRating: 1,
},
}
: null;
return (
<article className="flex flex-col gap-2">
<JsonLd
data={[
...(reviewLd ? [reviewLd] : []),
breadcrumbs([
{ name: "Home", url: site.baseUrl },
{ name: "Reading", url: abs("/reading") },
{ name: book.data.title, url },
]),
]}
/>
<div className="flex flex-col">
<h1 className="text-[36px] leading-[1.2] font-bold">
{book.data.title}
</h1>
<p className="mt-2 text-[20px] leading-[30px] text-gray-400">
{byline(book.data)}
</p>
</div>
<div className="prose">
<Mdx source={book.content} />
</div>
</article>
);
}