Skip to content

Repository files navigation

Synthesist in the Shell

A blog by Linghao Zhang, built with Next.js 15 and modern web technologies.

Tech Stack

  • Framework: Next.js 15 (App Directory, Static Export)
  • Language: TypeScript
  • Styling: Tailwind CSS 4
  • Content: MDX for rich content
  • Syntax Highlighting: Shiki
  • Math: KaTeX
  • Fonts: Inter Variable, Lora Italic Variable, Iosevka Fixed Curly
  • Deployment: Cloudflare Pages

Features

  • ✨ Modern, clean design with smooth view transitions
  • 📱 Fully responsive layout
  • 🎨 Custom typography with variable fonts
  • 🌈 Flexoki-inspired color scheme (warm cream, near-black text, teal accents)
  • 📝 MDX support for rich content
  • 🔍 Syntax highlighting for code blocks
  • 🧮 Math rendering support
  • 🏷️ Tag system with filtering
  • 🧩 Projects page with SVG logos
  • 📸 Photography gallery with lightbox
  • 📡 Auto-generated RSS feed
  • 🗂️ Toggleable table of contents sidebar for long posts
  • ⚓ Anchor links on all headings
  • 🌐 Multilingual article support with language switcher
  • ⚡ Optimized static site with CDN delivery

Getting Started

Prerequisites

  • Node.js 18.18 or later
  • npm

Installation

npm install

Development

npm run dev

Open http://localhost:3000 to view the blog.

Build

npm run build

The build outputs to the dist/ directory and includes:

  • Static HTML/CSS/JS files
  • Auto-generated RSS feed at /feed.xml

Additional Commands

# Generate RSS feed only
npm run rss

# Deploy to Cloudflare Pages (preview)
npm run deploy

# Deploy to Cloudflare Pages (production)
npm run deploy:prod

# Lint code
npm run lint

Project Structure

.
├── app/                     # Next.js app directory
│   ├── _fonts/             # Custom font files
│   ├── globals.css         # Global styles
│   ├── layout.tsx          # Root layout
│   ├── page.mdx            # Home page
│   ├── posts/              # Blog posts
│   │   ├── page.tsx        # Posts index
│   │   ├── [slug]/         # Dynamic post pages
│   │   └── _articles/      # Post content (MDX)
│   ├── notes/              # Legacy URL compatibility routes (redirect/alias to posts)
│   │   ├── page.tsx        # Legacy notes index
│   │   └── [slug]/         # Legacy note URLs
│   ├── misc/               # Miscellaneous articles
│   │   ├── page.tsx        # Misc index
│   │   ├── [slug]/         # Dynamic misc pages
│   │   └── _articles/      # Misc content (MDX)
│   ├── gallery/            # Photography gallery
│   │   ├── page.tsx        # Gallery page
│   │   ├── gallery-grid.tsx # Lightbox component
│   │   └── data.ts         # Photo data
│   ├── lists/              # Curation lists
│   │   ├── page.tsx        # Lists hub
│   │   └── [slug]/         # Individual list pages
│   ├── projects/           # Projects page
│   │   ├── page.tsx        # Projects page
│   │   ├── data.tsx        # Project metadata
│   │   └── logos.tsx       # Project SVG logos
│   ├── resume/             # Public resume at /resume
│   │   ├── source.ts       # Resume content source of truth
│   │   └── page.tsx        # Resume HTML renderer
│   └── tags/               # Tag system
│       └── all/            # Tag filtering page
├── components/             # React components
│   ├── navbar.tsx          # Navigation
│   ├── tag.tsx             # Tag component
│   ├── toc-sidebar.tsx     # Table of contents sidebar
│   └── ...                 # Other components
├── lib/                    # Utilities
│   ├── articles.ts         # Content loading
│   ├── seo.ts              # OG/social image resolution
│   └── tags.ts             # Tag management
├── scripts/                # Build scripts
│   └── generate-rss.mjs    # RSS generation
├── public/                 # Static assets
├── dist/                   # Build output
├── mdx-components.tsx      # MDX component config
├── next.config.ts          # Next.js config
├── wrangler.toml           # Cloudflare config

Content Management

Adding a New Post

  1. Create a new .mdx file in app/posts/_articles/
  2. Add metadata at the top:
export const metadata = {
  title: 'Your Post Title',
  description: 'A brief description',
  date: '2025.01.01',
  tags: ['Tag1', 'Tag2'],

  // Optional: show a table of contents sidebar (default open)
  toc: true,

  // Optional: OG/social card image (hosted on Cloudflare R2 or any public URL)
  image: 'https://r2.linghao.io/blog-assets/your-image.png',
  imageWidth: 1200,
  imageHeight: 630,

  // Optional: multilingual linking (see Multilingual Support below)
  language: 'en',
  translationId: 'shared-slug',
  canonical: true,
}

Your content here...
  1. The post will automatically:
    • Appear in the posts index
    • Be included in RSS feed
    • Be filterable by tags
    • Get a URL like /posts/your-post-title
    • Have anchor links on all headings (#heading-slug)

Table of Contents

Any post with two or more headings gets a toggleable TOC sidebar. A small icon button appears on the right side of the viewport; clicking it opens a panel listing all headings (h2–h4) with hierarchical numbering and active-heading highlighting as you scroll.

By default the sidebar is closed. Set toc: true in the article metadata to have it open automatically — useful for long reference posts.

Multilingual Support

Articles sharing a translationId are grouped as translations of each other. The article page shows a language switcher between them. Only the canonical: true version appears in index pages and tag pages.

export const metadata = {
  title: 'My Post',
  language: 'en',
  translationId: 'my-post',
  canonical: true,
}

Content Sections

  • Posts (app/posts/_articles/) - Blog posts and reading notes
  • Misc (app/misc/_articles/) - Miscellaneous content

Legacy /notes/* URLs are still supported for backward compatibility, but new content should be added under posts.

Resume Page

The public resume is available at /resume. Its content source is app/resume/source.ts, and the rendered HTML page is app/resume/page.tsx. Update content in source.ts; only edit page.tsx for layout/styling changes. See docs/resume.md for the full update workflow.

Gallery

The gallery feature displays a collection of photography. Photos are managed in app/gallery/data.ts.

To add a new photo:

  1. Upload the image to a hosting service (e.g., Cloudflare R2).
  2. Add a new object to the photos array in app/gallery/data.ts:
{
  id: 'unique-id',
  src: 'https://your-image-url.jpg',
  alt: 'Description for accessibility',
  caption: 'Optional caption text',
  metadata: {
    'Location': 'Kyoto, Japan',
    'Date': '2024-10-28',
    'Camera': 'Sony α7C II',
  }
}

Using Components

MDX files can use React components:

<Card
  image="https://example.com/image.jpg"
  title="Card Title"
  desc="Description"
  link="https://example.com"
/>

<BlockSideTitle title="Side note text">
  Main content here
</BlockSideTitle>

Customization

Colors

The blog uses the "rurikon" color palette defined in app/globals.css, mapped to Flexoki values. Customize colors by modifying the @theme section. See docs/visual-style.md for the full palette reference.

Fonts

Fonts are loaded locally via next/font/local in app/layout.tsx and exposed as CSS variables:

  • --sans: Inter Variable (body text, headings, UI elements — used everywhere)
  • --serif: Lora Italic Variable (navigation elements via global nav {} rule)
  • --mono: Iosevka Fixed Curly (code blocks and inline code)

Navigation

Edit components/navbar.tsx to modify navigation links.

Deployment

# Deploy to preview
npm run deploy

# Deploy to production
npm run deploy:prod

See DEPLOYMENT.md for detailed setup instructions.

Tag System

Using Tags

Add tags to any article's metadata:

export const metadata = {
  title: 'My Post',
  tags: ['JavaScript', 'React', 'Web Development'],
}

Viewing Tags

  • Browse all tags: /tags/all
  • Filter by tags: /tags/all?tag=JavaScript
  • Click tags on articles to filter

RSS Feed

The RSS feed is automatically generated on every build:

  • Feed URL: https://linghao.io/feed.xml
  • Includes: All posts and misc articles
  • Updates: Automatic on deployment
  • Manual generation: npm run rss

License

MIT

Acknowledgments

This blog is inspired by Shu Ding's blog.

About

Personal blog built with Next.js and MDX

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages