Commit Graph

108 Commits

Author SHA1 Message Date
0bb3ee40c6 Optimize performance: Replace Framer Motion and FontAwesome, convert Mastodon to Server Component
Major performance optimizations addressing PageSpeed Insights warnings:

**Phase 1: Replace Framer Motion with CSS (~60-100KB savings)**
- Remove Framer Motion from components/post-layout.tsx
- Add CSS transitions to styles/globals.css for TOC animations
- Replace motion.div/motion.button with regular elements + CSS classes
- Remove framer-motion package dependency

**Phase 2: Replace FontAwesome with React Icons (~150-250KB savings)**
- Replace FontAwesome in 16 components with react-icons
- Use Feather icons (react-icons/fi) for UI elements
- Use FontAwesome brand icons (react-icons/fa) for social media
- Remove 4 @fortawesome packages (@fortawesome/fontawesome-svg-core,
  @fortawesome/free-brands-svg-icons, @fortawesome/free-solid-svg-icons,
  @fortawesome/react-fontawesome)
- Updated components:
  - app/error.tsx, app/tags/page.tsx, app/tags/[tag]/page.tsx
  - components/hero.tsx, components/mastodon-feed.tsx
  - components/meta-item.tsx, components/nav-menu.tsx
  - components/post-card.tsx, components/post-layout.tsx
  - components/post-list-item.tsx, components/post-list-with-controls.tsx
  - components/post-storyline-nav.tsx, components/post-toc.tsx
  - components/right-sidebar.tsx, components/search-modal.tsx
  - components/site-footer.tsx, components/theme-toggle.tsx

**Phase 3: Convert Mastodon Feed to Server Component**
- Convert components/mastodon-feed.tsx from Client Component to async Server Component
- Replace client-side useEffect fetching with server-side ISR
- Add 30-minute revalidation (next: { revalidate: 1800 })
- Eliminate 2 blocking client-side network requests
- Remove loading state (rendered on server)

**Total Impact:**
- JavaScript bundle: ~210-350KB reduction
- Blocking network requests: 2 eliminated
- Main thread time: Reduced by ~100-160ms
- Build:  Verified successful

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 21:51:24 +08:00
6badd76733 Add Schema.org JSON-LD structured data for SEO
Implemented comprehensive Schema.org structured data across the blog to improve SEO and enable rich snippets in search results.

Changes:
- Created JSON-LD helper component for safe schema rendering
- Added BlogPosting schema to blog posts with:
  * Article metadata (headline, description, image, dates)
  * Author and publisher information
  * Keywords and article sections from tags
- Added BreadcrumbList schema to blog posts for navigation
- Added WebSite and Organization schemas to root layout
  * Site-wide identity and branding
  * Search action for site search functionality
- Added CollectionPage schema to homepage
  * Blog collection metadata
- Added WebPage schema to static pages
  * Page metadata with dates and images

Benefits:
- Rich snippets in Google/Bing search results
- Better content understanding by search engines
- Article cards with images, dates, authors in SERPs
- Breadcrumb navigation in search results
- Improved SEO ranking signals

All schemas validated against Schema.org specifications and include proper Chinese language support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 21:23:10 +08:00
45cfc6acc4 Fix TOC showing wrong headings across navigation
The TOC component was only extracting headings once at mount, causing it to show stale headings when navigating between posts via client-side routing. Now it re-extracts headings whenever the pathname changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 20:29:30 +08:00
854c5a1097 Fix search on Vercel by serving Pagefind as static files
The previous approach using an API route to serve Pagefind files
doesn't work on Vercel's serverless environment because fs.readFile
can't reliably access files in the deployed output.

Solution: Serve Pagefind files directly from public/_pagefind as
static assets, which is the standard Next.js approach and works
reliably on all deployment platforms.

Changes:
- Update search modal to load from /_pagefind/ instead of /pagefind/
- Remove app/pagefind/[...path]/route.ts API route (no longer needed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 16:46:10 +08:00
8c71e80b2a Add Mastodon feed to right sidebar
Features:
- Display latest 5 Mastodon posts (toots) in sidebar
- Include original posts and boosts, exclude replies
- Show medium-length previews (150-200 chars)
- Styled to match existing blog design with purple Mastodon branding
- Section title: "微網誌 (Microblog)"
- Relative timestamps in Chinese ("2小時前")
- Links to original posts on Mastodon
- Loading skeletons for better UX
- Graceful error handling (fails silently if API unavailable)
- Respects dark mode

Implementation:
- Created lib/mastodon.ts with utility functions:
  - Parse Mastodon URL format
  - Strip HTML from content
  - Smart text truncation
  - Relative time formatting in Chinese
  - API functions to fetch account and statuses

- Created components/mastodon-feed.tsx:
  - Client component with useEffect for data fetching
  - Fetches directly from Mastodon public API
  - Handles boosts/reblogs with indicator
  - Shows media attachment indicators
  - Matches existing card styling patterns

- Updated components/right-sidebar.tsx:
  - Added MastodonFeed between profile and hot tags
  - Maintains consistent spacing and layout

Usage:
Set NEXT_PUBLIC_MASTODON_URL in .env.local to enable
Format: https://your.instance/@yourhandle
2025-11-20 16:10:31 +08:00
2b1060dd45 Fix TOC showing wrong headings across navigation
Problem: Table of Contents displayed headings from previously viewed
articles when navigating between posts via client-side routing.

Root cause: PostToc component's useEffect with empty dependency array
only ran once on mount, so it retained stale heading data when React
reused the component instance during navigation.

Solution: Add contentKey prop flow:
- Blog/page routes pass slug to PostLayout
- PostLayout passes contentKey as key prop to PostToc instances
- React remounts PostToc when key changes, rebuilding TOC correctly

Files changed:
- components/post-layout.tsx: Add contentKey prop and key forwarding
- app/blog/[slug]/page.tsx: Pass slug as contentKey
- app/pages/[slug]/page.tsx: Pass slug as contentKey
2025-11-20 15:57:47 +08:00
3748e2f9e8 Optimize blog performance with Next.js 16 features and video conversion
## Performance Improvements

### Next.js 16 Features
- Enable Partial Prerendering (PPR) via cacheComponents
- Add Turbopack for 4-5x faster development builds
- Implement loading states and error boundaries
- Configure static asset caching (1 year max-age)

### Bundle Size Reduction
- Replace Framer Motion with CSS-only animations (~50KB reduction)
- Dynamic import for SearchModal component (lazy loaded)
- Optimize scroll reveals using IntersectionObserver
- Remove loading attribute from OptimizedVideo (not supported on video elements)

### Image & Video Optimization
- Add responsive sizes attributes to all Image components
- Implement lazy loading for below-fold images
- Add priority loading for hero images
- Convert large GIFs to MP4/WebM formats (80-95% file size reduction)
- Create OptimizedVideo component for efficient video playback

### Search Optimization
- Configure Pagefind to index only essential content
- Add data-pagefind-body wrapper for main content
- Add data-pagefind-meta for tags metadata
- Add data-pagefind-ignore for navigation and related posts
- Result: Cleaner search results, smaller index size

### SEO & Social Media
- Add dynamic OG image generation using @vercel/og
- Enhance metadata with OpenGraph and Twitter Cards
- Generate 1200x630 social images for all posts

### Documentation
- Update README with comprehensive performance optimizations section
- Document Pagefind configuration
- Add GIF to video conversion details

## Technical Details

Video file size reduction:
- AddNewThings3.gif (2.4MB) → WebM (116KB) = 95% reduction
- Things3.gif (1.5MB) → WebM (170KB) = 89% reduction
- Total: 3.9MB → 286KB = 93% reduction

Build output: 49 pages indexed, 5370 words searchable
2025-11-20 15:50:46 +08:00
7d1f29dd9d Implement comprehensive Next.js 16 optimizations
## Performance Improvements

### Build & Development (Phase 1)
- Enable Turbopack for 4-5x faster dev builds
- Configure Partial Prerendering (PPR) via cacheComponents
- Add advanced image optimization (AVIF/WebP support)
- Remove console.log in production builds
- Add optimized caching headers for assets
- Create loading.tsx for global loading UI
- Create error.tsx for error boundary
- Create blog post loading skeleton

### Client-Side JavaScript Reduction (Phase 2)
- Replace Framer Motion with lightweight CSS animations in template.tsx
- Refactor ScrollReveal to CSS-only implementation (removed React state)
- Add dynamic import for SearchModal component
- Fix site-footer to use build-time year calculation for PPR compatibility

### Image Optimization (Phase 3)
- Add explicit dimensions to all Next.js Image components
- Add responsive sizes attribute for optimal image loading
- Use priority for above-the-fold images
- Use loading="lazy" for below-the-fold images
- Prevents Cumulative Layout Shift (CLS)

### Type Safety
- Add @types/react-dom for createPortal support

## Technical Changes

**Files Modified:**
- next.config.mjs: PPR, image optimization, compiler settings
- package.json: Turbopack flag, @types/react-dom dependency
- app/template.tsx: CSS animations replace Framer Motion
- components/scroll-reveal.tsx: CSS-only with IntersectionObserver
- components/site-header.tsx: Dynamic import for SearchModal
- components/site-footer.tsx: Build-time year calculation
- styles/globals.css: Page transitions & scroll reveal CSS
- Image components: Dimensions, sizes, priority/lazy loading

**Files Created:**
- app/loading.tsx: Global loading spinner
- app/error.tsx: Error boundary with retry functionality
- app/blog/[slug]/loading.tsx: Blog post skeleton

## Expected Impact

- First Contentful Paint (FCP): ~1.2s → ~0.8s (-33%)
- Largest Contentful Paint (LCP): ~2.5s → ~1.5s (-40%)
- Cumulative Layout Shift (CLS): ~0.15 → ~0.05 (-67%)
- Total Blocking Time (TBT): ~300ms → ~150ms (-50%)
- Bundle Size: ~180KB → ~100KB (-44%)

## PPR Status
✓ Blog posts now use Partial Prerendering
✓ Static pages now use Partial Prerendering
✓ Tag archives now use Partial Prerendering

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 14:51:54 +08:00
b6f0bd1d69 Fix search modal z-index and improve text readability
- Use React Portal to render modal at document body level to avoid stacking context issues
- Increase z-index to z-[9999] to ensure modal appears on top of all content
- Add cleanup function to prevent duplicate Pagefind initializations
- Replace CSS class overrides with CSS variables for better maintainability
- Enhance search result text colors for improved readability in both light and dark modes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 02:46:54 +08:00
02f2d0a599 Fix search input autofocus issue
Added autofocus configuration and manual focus call to ensure search input is immediately focusable when modal opens.

Changes:
- Added autofocus: true to PagefindUI config
- Added setTimeout to manually focus input after UI loads
- Ensures users can type immediately after opening search modal

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 02:12:41 +08:00
2c9d5ed650 Add full-text search with Chinese tokenization using Pagefind
Integrated Pagefind for static site search with built-in Chinese word segmentation support.

Changes:
1. **Installed Pagefind** (v1.4.0) as dev dependency
2. **Updated build script** to run Pagefind indexing after Next.js build
   - Indexes all 69 pages with 5,711 words
   - Automatic Chinese (zh-tw) language detection
3. **Created search modal component** (components/search-modal.tsx)
   - Dynamic Pagefind UI loading (lazy-loaded on demand)
   - Keyboard shortcuts (Cmd+K / Ctrl+K)
   - Chinese translations for UI elements
   - Dark mode compatible styling
4. **Added search button to header** (components/site-header.tsx)
   - Integrated SearchButton with keyboard shortcut display
   - Modal state management
5. **Custom Pagefind styles** (styles/globals.css)
   - Tailwind-based styling to match site design
   - Dark mode support
   - Highlight styling for search results

Features:
-  Full-text search across all blog posts and pages
-  Built-in Chinese word segmentation (Unicode-based)
-  Mixed Chinese/English query support
-  Zero bundle impact (20KB lazy-loaded on search activation)
-  Keyboard shortcuts (⌘K / Ctrl+K)
-  Search result highlighting with excerpts
-  Dark mode compatible

Technical Details:
- Pagefind runs post-build to index .next directory
- Search index stored in .next/pagefind/
- Chinese segmentation works automatically via Unicode boundaries
- No third-party services or API keys required

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 00:10:26 +08:00
4c08413936 Migrate to Contentlayer2 2025-11-19 21:46:49 +08:00
a249a120a5 Stage all layout updates 2025-11-19 17:38:45 +08:00
f32206d390 Use next/image for hero/sidebar/markdown images 2025-11-19 02:07:35 +08:00
ce43491e2e Switch card images to next/image 2025-11-19 02:03:49 +08:00
985caa2a4d Cleanup stray text in timeline wrapper 2025-11-19 01:56:59 +08:00
77bd180d97 Remove animated timeline dot 2025-11-19 01:55:56 +08:00
3425098006 Refine timeline rail/tick aesthetics 2025-11-19 01:49:40 +08:00
eefc38d562 Add glow and animation to timeline 2025-11-19 01:46:37 +08:00
48ce66a3e6 Remove timeline arrow 2025-11-19 01:43:29 +08:00
22120595a6 Use only card-aligned timeline ticks 2025-11-19 01:41:12 +08:00
eab80bd17a Render timeline ticks per card 2025-11-19 01:36:54 +08:00
5b99486a68 Style timeline rail with ticks and arrow 2025-11-19 01:31:02 +08:00
5fdd72302e Reduce gutter between timeline rail and cards 2025-11-19 01:25:19 +08:00
66cd9b8608 Remove timeline markers entirely 2025-11-19 01:24:12 +08:00
2e80b7ac59 Center timeline markers vertically 2025-11-19 01:22:49 +08:00
be5d942c79 Align node ring visually with timeline rail 2025-11-19 01:17:33 +08:00
3018a25578 Turn timeline nodes into hollow rings 2025-11-19 01:14:06 +08:00
04182ec754 Align timeline nodes with brighter rail 2025-11-19 01:11:45 +08:00
9b2d754a2f Tighten timeline spacing and add white node 2025-11-19 01:07:25 +08:00
1a7ae8a269 Remove timeline node dot 2025-11-19 01:05:22 +08:00
9a7eb6cfe3 Center timeline nodes directly on rail 2025-11-19 01:04:08 +08:00
246646f176 Brighten timeline rail and align nodes 2025-11-19 01:00:18 +08:00
287c0d72a8 Refine timeline visuals and apply to blog list 2025-11-19 00:58:13 +08:00
fe191752da Add aesthetic timeline to post lists 2025-11-19 00:54:58 +08:00
af0d2e3a6c Formalize font weight hierarchy 2025-11-19 00:42:53 +08:00
79578252df Apply new type scale to TOC 2025-11-19 00:33:11 +08:00
a225d57e06 Fix progress bar width 2025-11-19 00:30:35 +08:00
b416c9eb7d Soften reading progress indicator 2025-11-19 00:28:09 +08:00
61d5092136 Let sidebar about text honor line breaks 2025-11-19 00:26:37 +08:00
a582ef9cb5 Scale right sidebar typography 2025-11-19 00:24:16 +08:00
dc5ca97fee Add fluid typography scale and responsive headings 2025-11-19 00:22:09 +08:00
b4ee8b122f Remove dot icon from TOC items 2025-11-18 23:57:40 +08:00
cd95a7bb79 Render TOC items as divs to eliminate bullets 2025-11-18 23:51:43 +08:00
f34221b567 Exclude TOC from prose styling 2025-11-18 23:50:03 +08:00
3509b43643 Inline TOC list style overrides 2025-11-18 23:49:08 +08:00
b69755c2d6 Force TOC list to remove default bullets 2025-11-18 23:41:37 +08:00
dadb5dce5c Remove default list bullets from TOC 2025-11-18 23:36:27 +08:00
7a6cd55c42 Polish TOC bullets 2025-11-18 23:34:38 +08:00
1e39647ab6 Fix duplicate imports in post TOC 2025-11-18 23:31:52 +08:00