diff --git a/README.md b/README.md index e215bc4..006aeb3 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,81 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Ephemeris -## Getting Started +Source of [blog.hangerthem.com](https://blog.hangerthem.com) — my personal blog and portfolio. Posts are plain Markdown files rendered to static HTML at build time by a custom [unified](https://unifiedjs.com/) pipeline. -First, run the development server: +## Features + +- **Fully static** — every post, tag and category page is pre-rendered with Next.js (App Router, React Server Components) +- **Custom Markdown extensions** via small rehype plugins in [`lib/`](lib/): + - Callouts: `> [!TIP]`, `> [!WARNING]`, `> [!INFO]`, `> [!QUOTE]` + - Highlights: `==marked text==` + - Definition lists (a term line followed by `: definition` lines) + - Code blocks with language label, line numbers and a copy button +- **Math** with KaTeX and **diagrams** with Mermaid, rendered to SVG at build time — no client-side JS for either +- **Table of contents** generated from `h2`/`h3` headings with scroll-spy +- **SEO** — canonical URLs, Open Graph/Twitter cards, generated OG images, JSON-LD, sitemap, robots.txt and an Atom feed +- Works without JavaScript; JS-only UI (like copy buttons) is hidden when it's unavailable + +## Stack + +Next.js 16 · React 19 · TypeScript · Tailwind CSS 4 · unified (remark/rehype) · highlight.js · KaTeX · Mermaid (via Puppeteer) · Bun · oxlint/oxfmt + +## Getting started ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +bun install +SITE_URL=http://localhost:3000 bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +| Script | Purpose | +| ------------------ | ------------------------------------ | +| `bun dev` | Development server | +| `bun run build` | Production build (static generation) | +| `bun start` | Serve the production build | +| `bun run lint` | oxlint + formatting check | +| `bun run lint:fix` | Auto-fix lint issues and format | -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +### Environment variables -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +| Variable | Required | Description | +| ---------------- | -------- | ---------------------------------------------------------------- | +| `SITE_URL` | yes | Public origin, e.g. `https://blog.hangerthem.com` | +| `SOURCE_COMMIT` | no | Commit hash shown in the sidebar (falls back to `git rev-parse`) | +| `GIT_ORIGIN_URL` | no | Repository URL; enables the "Source Code" link and commit link | -## Learn More +## Writing posts -To learn more about Next.js, take a look at the following resources: +Posts live in [`content/posts/`](content/posts/); the file name is the URL slug. Standalone pages (About, Projects, Setup) live in [`content/pages/`](content/pages/). -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +```yaml +--- +title: 'Tessera: A Privacy-First Card Wallet' +description: 120–160 characters work best for search results. +date: 2026-09-18 12:00:00 +0200 +updated: 2026-09-18 12:30:00 +0200 # optional +category: Projects +tags: [projects, pwa, privacy] +coverImage: tessera-cover.png # optional, from public/posts/, 1200×630 recommended +coverImageAlt: Tessera logo next to two loyalty cards +math: true # optional, enables KaTeX +mermaid: true # optional, enables Mermaid diagrams +draft: true # optional, only rendered in development +--- +``` -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +Frontmatter is validated at build time, so a missing or malformed field fails the build with the file name instead of producing a broken page. -## Deploy on Vercel +Images go in [`public/posts/`](public/posts/) and are referenced by file name: `![Alt text](image.png "Optional caption")`. -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +## Project structure -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +``` +app/ Routes, metadata, feed, sitemap and OG image generation +components/ UI split into content/, layout/, post/ and ui/ +content/ Markdown posts and pages +lib/ Markdown pipeline, rehype plugins, tag/category helpers +public/posts/ Post images +``` + +## Deployment + +Deployed with [Nixpacks](https://nixpacks.com/) (see [`nixpacks.toml`](nixpacks.toml)). Mermaid diagrams are rendered with headless Chromium during the build, so the build environment needs Chromium's system libraries and network access to fetch the Mermaid script. diff --git a/app/(withRecent)/categories/[categorySlug]/page.tsx b/app/(withRecent)/categories/[categorySlug]/page.tsx index 588a4a6..ca7842d 100644 --- a/app/(withRecent)/categories/[categorySlug]/page.tsx +++ b/app/(withRecent)/categories/[categorySlug]/page.tsx @@ -1,8 +1,7 @@ import { pageMetadata } from '@/app/metadata' import { getAllCategories, getCategoryBySlug, getPostsByCategory } from '@/lib/categories' -import { dateFormatter } from '@/utils/time' import type { Metadata } from 'next' -import Link from 'next/link' +import { PostList } from '@/components/post/PostList' import { notFound } from 'next/navigation' import { slugify } from '@/utils/slug' @@ -42,30 +41,9 @@ export default async function CategoryPage({ params }: CategoryPageProps) { return (
-
~/categories/{categorySlug}
+

~/categories/{categorySlug}

- +
) } diff --git a/app/(withRecent)/categories/page.tsx b/app/(withRecent)/categories/page.tsx index 1425399..b7f28b5 100644 --- a/app/(withRecent)/categories/page.tsx +++ b/app/(withRecent)/categories/page.tsx @@ -3,8 +3,8 @@ import { getAllPostsMeta } from '@/lib/posts' import { slugify } from '@/utils/slug' import type { Metadata } from 'next' import Link from 'next/link' +import { PostList } from '@/components/post/PostList' import { pageMetadata } from '@/app/metadata' -import { dateFormatter } from '@/utils/time' export async function generateMetadata(): Promise { return pageMetadata({ @@ -21,7 +21,7 @@ export default function CategoriesPage() { return (
-
~/categories
+

~/categories