The TOC was displaying sections from previously viewed articles when
navigating between posts. This happened because the DOM query for
headings ran before Next.js finished updating the page content.
Changes to components/post-toc.tsx:
- Clear items and activeId immediately when pathname changes
- Add 50ms delay before querying DOM for new headings
- Properly handle IntersectionObserver cleanup with timeout
This ensures the TOC always shows the correct headings for the
current article, not the previous one.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adjusted TOC button positioning to prevent overlap with the back-to-top
button on mobile devices:
- Mobile: bottom-20 (80px) - sits well above back-to-top at bottom-6
- Desktop: lg:bottom-8 - maintains original desktop position
- Both buttons now aligned to right-4 on mobile for consistency
This gives ~56px vertical spacing between buttons on mobile,
preventing any overlap while keeping both easily accessible.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The TOC toggle button was appearing near the end of posts instead of
floating at a fixed position. This happened because the button was
rendered inside the PostLayout component hierarchy.
Changes:
- Use React Portal to render TOC button at document.body level
- Add mounted state for proper SSR/client hydration
- Button now floats like back-to-top button, visible from start
This ensures the button is always visible and accessible, similar to
the back-to-top button behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Configure @next/bundle-analyzer for production bundle analysis:
**Changes:**
- Install @next/bundle-analyzer package
- Update next.config.mjs to wrap config with bundle analyzer
- Add npm script `build:analyze` to run build with ANALYZE=true
- Bundle analyzer only enabled when ANALYZE=true environment variable is set
**Usage:**
```bash
# Run build with bundle analysis
npm run build:analyze
# Opens interactive bundle visualization in browser
# Shows chunk sizes, module dependencies, and optimization opportunities
```
**Note:** Kept Mastodon feed as Client Component (not Server Component)
because formatRelativeTime() uses `new Date()` which requires dynamic
rendering. Converting to Server Component would prevent static generation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
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>
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>
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
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
- 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>
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>
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>