Content Sync: Obsidian → Fumadocs

Overview

Implemented a seamless content synchronization workflow from Obsidian vault to documentation site using symbolic links and automated indexing.

Architecture

ln -s "/Users/<Name>/Library/Mobile Documents/iCloud~md~obsidian/Documents/<Library>" obsidian

Structure:

content/
├── docs/           # Public documentation (tracked in git)
└── obsidian/       # Symlink to Obsidian vault (gitignored)

2. Content Discovery Script

Created automated extraction script to find and parse markdown files:

#!/bin/bash
extract_title() {
    local file="$1"
    # Extract title from YAML frontmatter
    title=$(sed -n '/^---$/,/^---$/p' "$file" | grep '^title:' | sed 's/^title: *//; s/"//g; s/'"'"'//g')
    echo "$title"
}

get_relative_path() {
    local file="$1"
    # Convert to docs-relative path
    echo "$file" | sed 's|.*/content/docs/||' | sed 's|/index.mdx$||'
}

3. Index Page with Tabs

Implemented tabbed interface using Fumadocs components:

File: content/docs/index.mdx

---
title: BearLabs
---

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

<Tabs items={['Learning', 'Projects', 'Trying']}>
  <Tab value="Learning">
    | Topic | Title | Link |
    |-------|-------|------|
    | AIGC | Code | [/learning/aigc/claude/code](/learning/aigc/claude/code) |
    ...
  </Tab>
  
  <Tab value="Projects">
    | Category | Title | Link |
    |----------|-------|------|
    | BearLabs | Data in Supabase | [/projects/bearlabs/data-in-supabase](/projects/bearlabs/data-in-supabase) |
    ...
  </Tab>
  
  <Tab value="Trying">
    | Category | Title | Link |
    |----------|-------|------|
    | Containers | Hyperms | [/trying/containers/hyperms](/trying/containers/hyperms) |
    ...
  </Tab>
</Tabs>

4. Tip Component Enhancement

Added summary fallback for failed image loading:

File: src/components/popup/tip.tsx

Features:

  • Added summary prop for fallback content
  • Image error handling with onError event
  • Conditional rendering logic:
    1. Try to load image
    2. If image fails + summary exists → show summary text
    3. Otherwise → show iframe
interface PopWindowProps {
  title: string;
  image?: string;
  link?: string;
  summary?: string;  // New prop
}

// Render logic
{image != "" && !imageError ? (
  <img onError={() => setImageError(true)} />
) : image != "" && imageError && summary != "" ? (
  <div className="p-6 text-gray-300">{summary}</div>
) : (
  <iframe src={link} />
)}

5. Article References

Example usage with summary fallback:

<Tip 
  title="Claude Skills 实战:推荐 5 个高质量 Skills" 
  link="https://mp.weixin.qq.com/s/..." 
  image="/docs/learning/aigc/claude/skills/04.png" 
  summary="这段时间在深入使用 Claude + Skills 的过程中,我逐步形成了一套『可组合、可复用、可自动化』的 Skills 使用体系。"
/>

Benefits

  1. Single Source of Truth: Write in Obsidian, automatically available in docs
  2. No Manual Copying: Symlink ensures real-time sync
  3. Organized Navigation: Tabbed interface with auto-generated tables
  4. Resilient Display: Summary fallback when images fail to load
  5. Separation of Concerns: Public docs vs. private notes clearly separated

File Structure

bearlabs.net/
├── content/
│   ├── docs/                    # Public documentation
│   │   ├── index.mdx           # Main index with tabs
│   │   ├── learning/           # Learning resources
│   │   ├── projects/           # Project documentation
│   │   └── trying/             # Experimental tools
│   └── obsidian/               # Symlink to Obsidian vault
│       └── Claude/
│           └── Claude Skills/  # Source articles
└── src/
    └── components/
        └── popup/
            └── tip.tsx         # Enhanced tip component

Maintenance

Adding New Content:

  1. Write markdown in Obsidian vault
  2. Run extraction script to update index
  3. Update index.mdx with new entries
  4. Ensure images are placed in /public/docs/ path

Quote Handling in MDX:

  • Use 『』 instead of " in summary text to avoid JSX parsing errors
  • Example: summary="这是『真实』的内容"

Tech Stack

  • Framework: Next.js with Fumadocs
  • Components: Fumadocs UI (Tabs, Tab)
  • Content: MDX with YAML frontmatter
  • Storage: iCloud Obsidian vault + Git repository