Import external articles into a Fumadocs project with automatic multi-language translation (en, zh, fr), AI-powered classification into 8 categories, image processing, and MDX conversion...
Automate importing external articles into a Fumadocs project with tri-language support (English, Chinese, French), auto-classification, and proper MDX formatting.
Before using this skill, verify:
.claude/skills/translator/curl is installed for image downloadscontent/docs/ and public/images/ directorieswithAllImages: true ParameterFor image processing to work, you MUST use withAllImages: true in Step 2:
Tool: read_url
Parameters:
- url: {article_url}
- withAllImages: true // โ MANDATORY for image extraction
Why this matters:
withAllImages: true: Jina returns text only, no imageswithAllImages: true: Jina returns text + images arrayWhat happens if you forget:
// WRONG - Won't extract images
const response = await mcp.read_url(url); // Missing withAllImages!
console.log(response.images); // โ undefined
// CORRECT - Will extract images
const response = await mcp.read_url(url, { withAllImages: true });
console.log(response.images); // โ
[img1, img2, ...]
You have two options for handling images. Choose before starting Step 4:
What: Download image files to public/images/docs/{slug}/
When to use:
Pros:
Cons:
Example in MDX:

What: Keep original URLs in the article (don't download)
When to use:
How to test if external URLs work:
# Test 1: Does the URL return 200?
curl -I "https://example.com/image.png"
# Expected: HTTP/2 200
# Test 2: Does it support CORS?
curl -I "https://example.com/image.png" | grep -i "access-control"
# Expected: access-control-allow-origin: *
# Or: access-control-allow-origin: https://your-domain.com
Pros:
Cons:
Example in MDX:

Test results for https://claude.com/blog/skills-explained:
# Test MCP diagram image
curl -I "https://cdn.prod.website-files.com/68a44d4040f98a4adf2207b6/69141f0993d68ff4c536f316_619a5262.png"
# Response includes:
# HTTP/2 200
# access-control-allow-origin: *
Result: โ Claude.com images support CORS (can use external URLs)
Decision for Claude.com articles: Use Option B (External URLs) - no download needed
Decision for unknown sources: Use Option A (Download) - safer
Step 2 now includes a mandatory validation (Sub-step 4) that:
response.images existsAlways verify images were extracted before proceeding!
This skill works best with:
Public Jina MCP Server (Recommended):
Add to your Claude configuration:
{
"mcpServers": {
"jina": {
"url": "https://mcp.jina.ai/sse",
"headers": {
"Authorization": "Bearer ${JINA_API_KEY}" // Optional, for higher rate limits
}
}
}
}
Note: Works without API key but has rate limits. For production use, get a free API key at https://jina.ai
Self-Hosted Jina MCP (Optional):
git clone https://github.com/jina-ai/MCP.git
cd MCP && npm install && npm run start
Available Tools:
read_url - Convert webpage to markdown โจ (primary tool)guess_datetime_url - Get publication datesearch_web, search_arxiv, search_images - Search capabilitiessort_by_relevance, deduplicate_strings - Content processingTranslation Strategy: This skill uses Claude's native translation capabilities through the translator skill.
The translator skill provides:
How it works: When this skill needs to translate content, it will automatically trigger Claude to use the translator skill. You don't need to configure anything - Claude will compose the two skills automatically based on the task requirements.
Ask the user for the following information:
Using Jina MCP (Recommended - best integration with Claude):
Fetch article content with images (RECOMMENDED - enables smart image filtering):
Tool: read_url
Parameters:
- url: {article_url}
- withAllImages: true โ ADD THIS
Returns:
- content: Markdown-formatted article content
- images: Array of image objects with URLs and metadata
- title, description, etc.
Why this matters: Returns structured image data instead of parsing markdown. You can access response.images directly.
Get publication date (optional but recommended):
Tool: guess_datetime_url
Parameters:
- url: {article_url}
Returns: Detected publication and update dates
Extract metadata from the fetched content:
response.images array if withAllImages=true, else extract from markdown)โ ๏ธ CRITICAL VALIDATION - Check for images (DO NOT SKIP):
// VERIFICATION STEP - Must check before proceeding!
// Check if withAllImages parameter was actually used
if (!response.images) {
console.error("โ CRITICAL ERROR: response.images is undefined!");
console.error("โ This means 'withAllImages: true' parameter was NOT passed to read_url");
console.error("โ Image processing will be completely skipped!");
// STOP here - do not proceed without image data
throw new Error(
`FAILED: Cannot extract images from ${article_url}\n` +
`Cause: withAllImages parameter missing in read_url call\n` +
`Solution: Re-run with correct parameter: { withAllImages: true }`
);
}
// Validate images array
if (response.images.length === 0) {
console.warn("โ ๏ธ WARNING: response.images array is empty!");
console.warn("โ The article may have no images, OR extraction failed");
// Ask user to confirm if this is expected
const hasImages = confirm("Does this article have images you want to download?");
if (hasImages) {
throw new Error(
`FAILED: Expected images but found none. Retry with withAllImages: true`
);
}
}
// SUCCESS - Log image count
console.log(`โ
SUCCESS: Found ${response.images.length} images in article`);
console.log(`โ Ready to proceed to image filtering (Step 3.5)`);
Why this validation is critical:
withAllImages: true, you get TEXT ONLY (no images array)Real-world consequence of skipping this check:
// WRONG - Skipping validation:
const response = await mcp.read_url(url); // Forgot withAllImages
const images = response.images; // โ undefined
heuristicFilter(images); // Returns empty array (undefined becomes [])
console.log("Found 0 images"); // User thinks article has no images
// Result: No images downloaded, user doesn't know they were missed
// CORRECT - With validation:
const response = await mcp.read_url(url); // Forgot withAllImages
if (!response.images) { // โ
Validation catches the error
throw new Error("Missing withAllImages parameter!"); // Stops execution
}
// Result: Clear error message, user knows to retry correctly
Alternative: Using Jina API directly (if MCP not available):
# Fetch article as markdown
curl "https://r.jina.ai/{article_url}"
# With custom options
curl "https://r.jina.ai/{article_url}" \
-H "X-Return-Format: markdown" \
-H "X-With-Generated-Alt: true"
Fallback: If neither Jina MCP nor API is available:
Critical: Apply defensive processing to prevent MDX syntax errors. This step acts as a safety net to handle unknown components and common MDX pitfalls from ANY source, not just Anthropic.
Why this matters: Articles come from diverse sources (Anthropic, GitHub, Medium, personal blogs, etc.), each with different component libraries and Markdown flavors. Instead of crashing on unknown syntax, we safely degrade content while preserving readability.
Processing Pipeline:
// Safety processor that handles content from any source
const safetyProcessor = {
// Phase 1: Handle unknown/dangerous JSX components
handleUnknownComponents(content: string): string {
// Known Fumadocs components (whitelist - safe to keep)
const fumadocsComponents = [
'Callout', 'Cards', 'Card', 'Tabs', 'Tab', 'Steps', 'Step',
'Files', 'Folder', 'File', 'Accordion', 'ImageZoom'
];
// Pattern 1: Handle closed components <Component>...</Component>
content = content.replace(
/<([A-Z][a-zA-Z]*)[^>]*>([\s\S]*?)<\/\1>/g,
(match, componentName, innerContent) => {
if (fumadocsComponents.includes(componentName)) {
return match; // Keep known components
}
// Unknown component: degrade to plain text with comment
console.warn(`โ ๏ธ Unknown component <${componentName}>, degrading to plain text`);
return `<!-- Original: <${componentName}> -->\n${innerContent}\n<!-- End: ${componentName} -->`;
}
);
// Pattern 2: Handle self-closing components <Component />
content = content.replace(
/<([A-Z][a-zA-Z]*)[^\/]*\/>/g,
(match, componentName) => {
if (fumadocsComponents.includes(componentName)) {
return match; // Keep known components
}
console.warn(`โ ๏ธ Unknown self-closing component <${componentName}/>, removing`);
return `<!-- Removed: <${componentName}/> -->`;
}
);
return content;
},
// Phase 2: Fix common MDX pitfalls that break parsing
fixMDXPitfalls(content: string): string {
// Pitfall 1: <number pattern (e.g., "<5k tokens") breaks MDX
// Replace with HTML entity or rephrase
content = content.replace(
/<(\d+)/g,
(match, num) => {
console.warn(`โ ๏ธ Fixed <${num} pattern (breaks MDX)`);
return `<${num}`;
}
);
// Pitfall 2: Common HTML-like tags in text
const dangerousTags = ['script', 'div', 'span', 'p', 'a', 'img'];
dangerousTags.forEach(tag => {
content = content.replace(
new RegExp(`<(${tag})\\b`, 'gi'),
(match) => {
console.warn(`โ ๏ธ Fixed <${tag}> pattern in text`);
return match.replace('<', '<');
}
);
});
// Pitfall 3: Bold formatting without space (non-Latin languages)
// Wrong: **็ฒไฝ๏ผ**ๆๅญ โ Right: **็ฒไฝ๏ผ** ๆๅญ
content = content.replace(
/\*\*([^*]+)\*\*([^ \n*-])/g,
'**$1** $2'
);
// Pitfall 4: Unclosed JSX tags (basic check)
const tags = content.match(/<\/[a-zA-Z]+>/g);
if (tags) {
tags.forEach(closingTag => {
const tagName = closingTag.replace('</', '').replace('>', '');
const openings = (content.match(new RegExp(`<${tagName}[^>]*>`, 'g')) || []).length;
const closings = (content.match(new RegExp(`<\/${tagName}>`, 'g')) || []).length;
if (openings !== closings) {
console.error(`โ Mismatched <${tagName}> tags: ${openings} openings, ${closings} closings`);
}
});
}
return content;
},
// Phase 3: Auto-inject missing imports for known components
injectImports(content: string): string {
const usedComponents = new Set<string>();
// Detect Fumadocs components
const fumadocsComponents = {
'Callout': { import: "import { Callout } from 'fumadocs-ui/components/callout';" },
'Cards': { import: "import { Cards, Card } from 'fumadocs-ui/components/card';" },
'Card': { import: "import { Cards, Card } from 'fumadocs-ui/components/card';" },
'Tabs': { import: "import { Tabs, Tab } from 'fumadocs-ui/components/tabs';" },
'Tab': { import: "import { Tabs, Tab } from 'fumadocs-ui/components/tabs';" },
'Steps': { import: "import { Steps, Step } from 'fumadocs-ui/components/steps';" },
'Step': { import: "import { Steps, Step } from 'fumadocs-ui/components/steps';" },
'Files': { import: "import { Files, Folder, File } from 'fumadocs-ui/components/files';" },
'Folder': { import: "import { Files, Folder, File } from 'fumadocs-ui/components/files';" },
'File': { import: "import { Files, Folder, File } from 'fumadocs-ui/components/files';" },
'Accordion': { import: "import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';" },
'ImageZoom': { import: "import { ImageZoom } from 'fumadocs-ui/components/image-zoom';" }
};
// Check which components are used
Object.keys(fumadocsComponents).forEach(comp => {
const pattern = new RegExp(`<${comp}\\b`, 'g');
if (pattern.test(content)) {
usedComponents.add(fumadocsComponents[comp].import);
}
});
if (usedComponents.size === 0) return content;
// Check if imports already exist
const existingImports = content.includes('from \'fumadocs-ui/components');
if (existingImports) {
console.log('โ
Fumadocs imports already present');
return content;
}
// Inject imports after frontmatter
console.log(`๐ฆ Injecting ${usedComponents.size} import statements`);
const importBlock = Array.from(usedComponents).join('\n') + '\n\n';
return content.replace(
/(---\n\n)/,
`$1${importBlock}`
);
}
};
// Main safety processing function
function processContentSafely(content: string, sourceUrl: string): { content: string, warnings: string[] } {
console.log(`๐ Processing content safely from: ${sourceUrl}`);
const warnings: string[] = [];
try {
// Step 1: Handle unknown components
content = safetyProcessor.handleUnknownComponents(content);
// Step 2: Fix MDX pitfalls
content = safetyProcessor.fixMDXPitfalls(content);
// Step 3: Inject imports
content = safetyProcessor.injectImports(content);
console.log('โ
Content safety processing complete');
} catch (error) {
console.error('โ Safety processing failed:', error);
warnings.push(`Safety processing error: ${error.message}`);
}
return { content, warnings };
}
Execution in Workflow:
Call this function immediately after Step 2 (content extraction) and before Step 3 (slug generation):
// In the main workflow:
const rawContent = response.content; // From Step 2
const { content: safeContent, warnings } = processContentSafely(rawContent, articleUrl);
// Store warnings for the summary report
const processingWarnings = warnings;
// Continue with safeContent for all subsequent steps
Why This Position Matters:
Key Design Principles:
Defensive, Not Prescriptive: We don't try to perfectly convert every component. Unknown components are safely degraded rather than causing crashes.
Source-Agnostic: Works for ANY source (Anthropic, GitHub, Medium, personal blogs) without source-specific rules.
Non-Destructive: Original intent is preserved through comments. For example:
<!-- Original: <AnthropicCard> -->
Card content here
<!-- End: AnthropicCard -->
Automated: Zero user configuration required. The skill automatically detects and handles issues.
Warning Collection:
Collect all warnings during processing and include them in the final summary:
โ ๏ธ Content Safety Processing:
- Unknown component <AnthropicCard> (degraded to plain text)
- Unknown component <FileGroup> (degraded to plain text)
- Fixed <5k pattern (breaks MDX)
- Injected 2 import statements
- Mismatched <Callout> tags: 3 openings, 2 closings
๐ฆ Injected Imports:
โ
import { Callout } from 'fumadocs-ui/components/callout';
โ
import { Cards, Card } from 'fumadocs-ui/components/card';
Benefits:
Create a URL-friendly slug from the article title:
Example: "Building React Apps with TypeScript" โ "building-react-apps-with-typescript"
Critical improvement: Most articles contain 15-20 images, but only 1-3 are actual content images (diagrams, charts, screenshots). The rest are decorative icons, logos, placeholders, or social preview images. We use heuristic rules to filter them.
Input: Array of image URLs (from Step 2)
Heuristic Filtering Rules:
// Blacklist - IMMEDIATE REJECTION
const blacklist = [
'placeholder.svg', // Placeholder images
'favicon', // Website icons
'logo', // Company logos
'spinner', // Loading animations
'avatar', // User avatars
'decoration', // Decorative elements
'icon-', // Icon files
'social-share', // Social media preview
'og-image', // OpenGraph preview
'twitter-card' // Twitter card images
];
// Whitelist - MUST KEEP
const whitelist = [
'diagram', // Architecture diagrams
'chart', // Data visualizations
'screenshot', // UI screenshots
'visualization', // Data viz
'architecture', // System architecture
'flowchart', // Process flows
'graph', // Charts/graphs
'timeline' // Timeline graphics
];
function heuristicFilter(images: ImageInfo[]): ImageInfo[] {
return images.filter(img => {
const url = img.url.toLowerCase();
const filename = img.filename.toLowerCase();
// ๐ซ BLACKLIST: Immediate rejection
if (blacklist.some(term => url.includes(term) || filename.includes(term))) {
return false; // Skip decorative images
}
// โ
WHITELIST: Must keep
if (whitelist.some(term => url.includes(term) || filename.includes(term))) {
return true; // Keep content images
}
// ๐ FILE TYPE & SIZE RULES
if (url.endsWith('.png') && img.fileSize > 10000) return true; // PNG > 10KB likely content
if (url.endsWith('.jpg') && img.fileSize > 15000) return true; // JPG > 15KB likely content
if (url.endsWith('.svg') && !url.includes('placeholder')) return true; // SVG (except placeholder)
// ๐ฏ CONTEXT RULES
// If image appears near keywords like "diagram", "figure", "example"
if (isNearContext(img, ['diagram', 'figure', 'example', 'illustration'])) {
return true;
}
return false; // Default: exclude if uncertain
}).slice(0, 5); // MAX 5 images to avoid clutter
}
Example Filtering:
User Confirmation (shows transparency):
๐ Image Analysis Complete:
โ
Found 18 images total
๐ฏ Identified 1 content image (MCP protocol diagram)
๐ซ Filtered 17 decorative/placeholder images
Image to download:
[Preview: https://cdn.../mcp-diagram.png]
Description: MCP protocol architecture diagram
Download? (Enter=yes, no=skip):
Extract key technical concepts from article content using Claude AI. This enables intelligent cross-referencing and related article recommendations.
Why AI instead of rules:
AI Prompt:
const conceptExtractionPrompt = `
Read the following article and extract 5-10 key technical concepts.
Title: "${articleTitle}"
Content: """${articleContent.substring(0, 5000)}"""
For each concept, provide:
1. term: The exact term/concept name
2. definition: Brief explanation (1 sentence)
3. isMainTopic: true if this article primarily explains this concept, false if just mentions it
4. importance: Score 1-10 (how central this concept is to the article)
Output format:
\\
\\`\\`\\`json
{
"concepts": [
{
"term": "Skills",
"definition": "Claude's feature for saving and reusing instruction sets",
"isMainTopic": true,
"importance": 10
}
]
}
\\`\\`\\`
**Example AI Decision**:
For the sentence "Claude's Skills feature helps you build agents":
- AI understands "Skills" is a proper noun (Claude feature)
- AI understands "agents" refers to AI agents
- AI judges importance based on context and article focus
**Execution**:
1. **Call Claude AI**:
```typescript
const response = await askClaude(conceptExtractionPrompt);
const { concepts } = JSON.parse(response);
Save concept extraction:
mkdir -p "archive/concepts"
writeJson(`archive/concepts/${articleSlug}.json`, {
article: articleSlug,
lang: languageCode,
title: articleTitle,
concepts: concepts
});
Output:
๐ค AI Concept Extraction Complete:
โ
Extracted ${concepts.length} concepts
๐ฏ Main topics: ${concepts.filter(c => c.isMainTopic).map(c => c.term).join(', ')}
๐ All concepts: ${concepts.map(c => `${c.term}(${c.importance})`).join(', ')}
AI Decision Examples:
Example 1:
Input: "Claude's Skills feature allows you to save prompts"
AI Output:
- term: "Skills"
- isMainTopic: true (ๆ็ซ ไธป่ฆ่ฎฒ่งฃSkills)
- importance: 10
Example 2:
Input: "Using Python with Claude Code"
AI Output:
- term: "Python"
- isMainTopic: false (ๅชๆฏๆๅฐPython๏ผไธๆฏไธ้จ่ฎฒPython)
- importance: 6
Example 3:
Input: "The Model Context Protocol (MCP) is a protocol"
AI Output:
- term: "MCP"
- definition: "Model Context Protocol, connects AI assistants to external systems"
- isMainTopic: true
- importance: 9
AI vs Rules Comparison:
| Input | Rule-based (Keyword) | AI-based (Understanding) |
|---|---|---|
| "Claude's Skills feature" | Finds "Skills" word | Understands "Skills" is a Claude feature |
| "Skills are important" | Finds "Skills" word | Understands this is about abilities, not Claude Skills |
| "The agent processes tasks" | Finds "agent" word | Understands "agent" = AI agent in this context |
Key AI Decision Points:
Automatically insert links to related articles when concepts are mentioned. AI decides where and how many links to insert for natural reading flow.
Why AI instead of naive replacement:
Prerequisites:
AI Prompt:
const crossReferencePrompt = `
You are adding intelligent cross-references to a technical article.
Target article: "${articleTitle}"
Content: """${articleContent}"""
Relevant concepts from this article (from Step 3.6):
${JSON.stringify(concepts.filter(c => !c.isMainTopic), null, 2)}
For each concept, here is the authoritative article to link to:
${JSON.stringify(conceptIndex, null, 2)}
Task:
1. Identify where these concepts are FIRST mentioned in the content
2. Determine if linking would help the reader (skip if obvious or already explained)
3. Insert links naturally (don't break reading flow)
4. Limit to 3-5 links max (avoid over-linking)
5. NEVER link in: headings, code blocks, links, or quotes
Output format:
\\`\\`\\`json
{
"enhancedContent": "content with links inserted",
"linksInserted": [
{
"position": 125,
"concept": "Skills",
"targetArticle": "skills-explained",
"context": "first mention in paragraph",
"reasoning": "Reader may need background on Skills concept"
}
]
}
\\`\\`\\`
**Example: Before and After AI Insertion**
**Before** (original content):
```markdown
## Building Agents with Skills
When you combine Skills with the Claude Agent SDK, you can create powerful workflows. Skills allow you to save and reuse instructions.
After AI Enhancement:
## Building Agents with Skills
When you combine [Skills](โskills-explained) with the Claude Agent SDK, you can create powerful workflows. Skills allow you to save and reuse instructions.
AI Decision:
Execution:
Call Claude AI:
const response = await askClaude(crossReferencePrompt);
const { enhancedContent, linksInserted } = JSON.parse(response);
Save link metadata:
writeJson(`archive/links/${articleSlug}.json`, {
article: articleSlug,
lang: languageCode,
totalLinks: linksInserted.length,
links: linksInserted
});
Output:
๐ค AI Cross-Reference Insertion Complete:
โ
Analyzed ${concepts.length} concepts
๐ฏ Inserted ${linksInserted.length} links
๐ Positions: ${linksInserted.map(l => l.position).join(', ')}
๐ก AI reasoning: ${linksInserted.map(l => l.reasoning).join('; ')}
AI Decision Examples:
Example 1: Skip obvious concepts
Content: "The HTTP protocol is used for web requests"
AI Decision: Don't link "HTTP" (too generic, most developers know it)
Example 2: Link important concept on first mention
Content: "Claude's Skills feature allows you to..."
AI Decision: Link "Skills" on first mention (core concept, reader may need context)
Example 3: Don't over-link
Content: "Skills are powerful. Skills allow reuse. Skills improve consistency."
AI Decision: Only link first "Skills" (linking all three would be overwhelming)
Example 4: Skip in headings
Content: "## Skills Overview\n\nSkills are..."
AI Decision: Don't link "Skills" in the heading (breaks formatting)
Key AI Decision Points:
AI vs Naive Comparison:
| Article Text | Naive (First Occurrence) | AI (Context-Aware) |
|---|---|---|
| "Skills and agents" | Links "Skills" in title | Only links "agents" (Skills already explained) |
| "The key skill is..." | Links "skill" (wrong case) | Doesn't link (lowercase = generic skill) |
| "Skills allow X. Skills enable Y." | Links both | Links only first (avoids overlinking) |
Based on user's choice in Step 1 (image handling mode), use one of these strategies:
When to use: Source website supports CORS (like Claude.com, GitHub, etc.)
Process:
// No download needed - just keep the original URLs
// MDX will reference external images directly
// Example output in MDX:
// Original: 
// Final:  โ Unchanged!
#### Strategy B: Download to Local (Safe Option) - Use for Unknown/Complex Sites
**When to use**: Unknown source, no CORS support, or want offline availability
**Process**:
1. **User Confirmation** (show transparency):
๐ Image Strategy: Download to Local
โ Found {total_images} total images on page ๐ฏ Identified {filtered_images} content images (diagrams/screenshots) ๐ซ Filtered {skipped_images} decorative images (placeholders/icons)
Images to download:
[Preview URL: https://.../mcp-diagram.png] โ Description: MCP protocol architecture diagram โ Size: 142KB PNG
[Preview URL: https://.../data-flow.png] โ Description: Data flow visualization โ Size: 89KB PNG
Download these images? (Enter=yes, no=skip) [yes]:
2. **Create directory**:
```bash
mkdir -p "public/images/docs/{article-slug}"
Download each image (with retry logic):
curl -f -L -o "public/images/docs/{slug}/{image-name}" "{image_url}" || \
curl -f -L -o "public/images/docs/{slug}/{image-name}" "{image_url}" || \
echo "โ ๏ธ Failed to download: {image_url}"
Update MDX references:
// Original

// Updated

Handle failures gracefully:
Pros: Works 100%, offline, full control Cons: Slower, uses storage, more complex
When to use: You want the skill to automatically decide
Process:
Test first image for CORS support:
curl -I "{first_image_url}" | grep -i "access-control"
# If returns "access-control-allow-origin: *" โ external mode
# If returns nothing or error โ download mode
Based on result, auto-switch:
const hasCORS = checkCORS(firstImageUrl);
if (hasCORS) {
console.log("โ
Images support CORS โ Using external URLs");
useStrategyExternal();
} else {
console.log("โ No CORS support โ Downloading images locally");
useStrategyDownload();
}
Process all images with chosen strategy
Pros: Intelligent, optimal choice, hands-off Cons: Extra test step, might mis-detect edge cases
Example Test Result:
Testing: https://cdn.prod.website-files.com/68a44d.../619a5262.png
Response:
HTTP/2 200
access-control-allow-origin: *
access-control-allow-methods: GET, HEAD
access-control-allow-headers: *
โ โ
CORS supported โ Use external URLs
| Scenario | Strategy | Why |
|---|---|---|
| Claude.com, GitHub, GitLab | External | Tested to support CORS |
| Medium, Dev.to, Hashnode | External | Usually supports CORS |
| Corporate/internal sites | Download | Often no CORS |
| Unknown/random sites | Auto | Let skill decide |
| Need offline access | Download | Self-contained |
| Want fastest import | External | Skip downloads |
| First time trying | Auto | Safest bet |
Default: auto (intelligent detection)
Post-Import Verification:
public/images/docs/{slug}/ for filesDetect and embed YouTube videos from article content:
Detection Patterns:
// Match YouTube URLs in content
const patterns = [
// iframe embeds
/<iframe[^>]*src="https?:\/\/(www\.)?youtube\.com\/embed\/([a-zA-Z0-9_-]+)"[^>]*>/g,
// youtu.be short URLs
/https?:\/\/youtu\.be\/([a-zA-Z0-9_-]+)/g,
// youtube.com/watch URLs
/https?:\/\/(www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/g,
];
function extractYouTubeVideos(content: string): YouTubeVideo[] {
const videos = [];
for (const pattern of patterns) {
const matches = content.matchAll(pattern);
for (const match of matches) {
const videoId = match[2] || match[3];
videos.push({
id: videoId,
embedUrl: `https://www.youtube.com/embed/${videoId}`,
watchUrl: `https://www.youtube.com/watch?v=${videoId}`,
startTime: extractTimeParam(match[0]) // Handle &t=123s
});
}
}
return videos;
}
In MDX: Use Fumadocs Video Component
Option 1: Keep iframe (simplest, works everywhere):
<iframe
width="100%"
height="500"
src="https://www.youtube.com/embed/VIDEO_ID"
title="Video title"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen>
</iframe>
Option 2: Use Fumadocs <Video> component (if available):
import { Video } from 'fumadocs-ui/components/video';
<Video
src="https://www.youtube.com/watch?v=VIDEO_ID"
title="Introduction to MCP"
/>
Auto-Processing Flow:
const videos = extractYouTubeVideos(content);
if (videos.length > 0) {
console.log(`๐บ Found ${videos.length} YouTube video(s)`);
// Auto-embed: Replace YouTube URLs with iframes
content = content.replace(youtubeRegex, (match) => {
const videoId = extractVideoId(match);
return generateEmbedCode(videoId);
});
}
No thumbnail download needed (per user preference):
Example Output: In generated MDX, video section becomes:
## Video Introduction
<p className="video-wrapper">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
width="100%"
height="500"
title="MCP Protocol Overview"
/>
</p>
Continue reading...
Summary Report (auto-processed, no user interruption):
๐บ Videos: 2 YouTube videos detected and embedded
- Video 1: Introduction to MCP (6:23)
- Video 2: Advanced MCP features (12:45)
โ
Embedded using iframe for compatibility
Load references/classification-rules.md and analyze the article to determine:
Category (one of 8):
Difficulty Level (one of 3):
Tags (3-7 tags):
Purpose: Automatically create a modern, theme-relevant SVG cover illustration for the article.
Why this matters:
Process:
Invoke the philosophical-illustrator skill:
Prepare illustration context:
Article Title: {translated_title}
Category: {category}
Description: {description}
Key Concepts: {extracted_concepts from Step 3.6}
Category-to-Color-Palette Mapping:
| Category | Palette | Colors |
|---|---|---|
| development | Pink-Purple | #C67B9B, #B8789E, #A97BA1 |
| ai-ml | Pink-Purple | #C67B9B, #B8789E, #A97BA1 |
| data | Beige-Neutral | #C9BFA8, #D4CAAF, #B8AD98 |
| design | Orange-Coral | #D17B5C, #C88860, #B87A5D |
| content | Orange-Coral | #D17B5C, #C88860, #B87A5D |
| devops | Green-Olive | #6B7F64, #758C6E, #607360 |
| security | Blue | #5B8FB9, #6B9BC4, #7AA5C8 |
| business | Multi-topic | #CA8760, #D17B5C, #B87A5D |
Generate SVG illustration:
Save illustration:
mkdir -p "public/images/docs/{article-slug}"
# Save SVG output to:
# public/images/docs/{article-slug}/cover.svg
Update frontmatter reference:
image: /images/docs/{article-slug}/cover.svgExample invocation:
Use the philosophical-illustrator skill to generate a cover illustration:
Title: "Understanding Claude Skills: A Deep Dive"
Category: ai-ml
Key Concepts: Skills, Claude, AI agents, workflow automation
Description: Comprehensive guide to creating and using Claude Skills for development workflows
Please create a modern SVG illustration with:
- Pink-Purple color palette (ai-ml category)
- Visual elements: code brackets, AI brain icons, workflow connections
- Modern, friendly aesthetic
- 800x450px dimensions
Output:
public/images/docs/{slug}/cover.svgimage fieldFallback:
Translation Strategy: Use professional translation for each target language.
Certain terms must remain in English as they are:
Translation Instruction:
Translate the following to {language_name}, but PRESERVE these terms in English:
- Claude, Anthropic, Skills, Projects, MCP, Agent, SubAgent
- GitHub, Google Drive, Slack, Excel
- React, Python, Node.js, TypeScript
- API, SDK, AI, ML, RAG, UI, UX
- All code identifiers (variable/function/class names)
Why: These are proper names, brand names, or universal technical terms.
Translating them would confuse readers who expect the standard English terms.
Example of CORRECT translation:
English: "Claude's Skills feature helps agents work better"
Chinese: "Claude ็ Skills ๅ่ฝๅธฎๅฉ agents ๆดๅฅฝๅฐๅทฅไฝ" (NOT: "ๅ
ๅณๅพท็ๆ่ฝๅ่ฝๅธฎๅฉไปฃ็ๆดๅฅฝๅฐๅทฅไฝ")
Example of CORRECT translation:
English: "Use the React component with Node.js"
French: "Utilisez le composant React avec Node.js" (NOT: "Utilisez le composant Rรฉagir avec Noeud.js")
For each target language (en, zh, fr):
Prepare content for translation:
Request translation:
Example request format:
Please translate the following article to Chinese (zh).
This is technical documentation - preserve all Markdown syntax, code blocks,
and image references exactly as they appear.
CRITICAL: DO NOT translate these terms - keep them in English:
- Claude, Anthropic, Skills, Projects, MCP, Agent, SubAgent, Subagents
- GitHub, Google Drive, Slack, Excel
- React, Python, Node.js, TypeScript, JavaScript
- API, SDK, AI, ML, RAG, UI, UX, REST, HTTP
- All variable names, function names, and class names in code blocks
Why preserve: These are proper names, brand names, or universal technical terms.
Translating them would confuse readers.
Example correct translation:
WRONG: "ๅ
ๅณๅพท็ๆ่ฝๅ่ฝๅธฎๅฉไปฃ็ๆดๅฅฝๅฐๅทฅไฝ"
CORRECT: "Claude ็ Skills ๅ่ฝๅธฎๅฉ agents ๆดๅฅฝๅฐๅทฅไฝ"
Article to translate:
[Article content here]
Translation will automatically apply:
Language-specific handling:
Quality considerations:
Save translations:
For each language, create the MDX file:
Prerequisite: Content must have been processed through Step 2.5 (Content Safety Processing) to ensure it's free of MDX syntax errors.
File path: content/docs/{lang}/{category}/{article-slug}.mdx
Frontmatter (use assets/frontmatter-template.yaml as reference):
---
title: "{translated_title}"
description: "{translated_description}"
image: /images/docs/{article-slug}/cover.svg # Generated by philosophical-illustrator
lang: {language_code}
category: {determined_category}
difficulty: {determined_difficulty}
tags:
- {tag1}
- {tag2}
- {tag3}
source_url: "{original_article_url}"
published_date: "{YYYY-MM-DD}"
author: "{original_author}"
# Enhanced source tracking (NEW)
source:
url: "{original_article_url}"
name: "{site_name}" # e.g., "Claude Blog", "GitHub Docs"
author: "{original_author}"
published_date: "{YYYY-MM-DD}"
accessed_date: "{current_date}" # When we imported it
license: "{license_if_known}" # e.g., "Copyright ยฉ 2025 Anthropic"
# archived_url: "https://web.archive.org/..." // Optional
# Import metadata
import:
date: "{current_date}"
slug: "{article-slug}"
translator: "Claude AI" # Since we use translator skill
---
IMPORTANT: The image field must be placed BEFORE the tags list in frontmatter. This ensures proper YAML parsing.
Add SourceAttribution component (at top of content):
---
title: "Article Title"
# ... frontmatter above
---
import { SourceAttribution } from '@/components/SourceAttribution';
<SourceAttribution
source={{
url: "https://example.com/original-article",
name: "Original Site Name",
author: "Original Author",
publishedDate: "2025-01-20",
accessedDate: "2025-11-16"
}}
languages={['en', 'zh', 'fr']}
currentLang={language_code}
/>
# Article content starts here...
Add Source Declaration (at bottom of content):
---
## โน๏ธ Source Information
**Original Article**: [Link]({original_article_url})
- **Source**: {site_name}
- **Author**: {original_author}
- **Published**: {published_date}
- **Imported**: {current_date}
- **License**: {license_info}
*This article was automatically imported and translated using Claude AI.*
Content Conversion:
references/fumadocs-components.md to understand available components<Cards> and <Card><Callout type="info|warn|error"><Steps> and <Step><Tabs> and <Tab><Files>, <Folder>, <File>Validate and Fix MDX Syntax (MANDATORY - Must run even for manual content):
<5k patterns break builds even in manually written articles. Always validate.< with < when used in text (not in JSX tags or code blocks)<5, <10, <100, <script, <div in plain text<5k if written as <5k< inside code blocks (triple backticks) or valid JSX components> with > when used in text comparisons<number, <word in plain text will break MDX<5k โ <5k or "less than 5k"**็ฒไฝ๏ผ** ๆๅญ not **็ฒไฝ๏ผ**ๆๅญ\*\*([^*]+)\*\*([^ \n*-]) with **$1** $2Create the file:
mkdir -p "content/docs/{lang}/{category}"
# Write the MDX content to file
Automatically add 3-5 relevant article recommendations at the bottom of each article. AI analyzes concept relationships and content similarity to suggest the most helpful next reads.
Prerequisites:
Why AI Instead of Tags/Category Matching:
AI Analysis Criteria:
AI Prompt:
const relatedArticlesPrompt = `
You are recommending related articles at the bottom of a technical article.
Current article: "${articleTitle}"
Category: ${category}
Language: ${languageCode}
Difficulty: ${difficulty}
Concepts in this article (from Step 3.6):
${JSON.stringify(concepts, null, 2)}
Available articles in ${languageCode}/${category}:
${JSON.stringify(availableArticles, null, 2)}
Task:
1. Analyze ALL available articles in the same category and language
2. Find 3-5 articles that would be MOST HELPFUL to read next
3. Consider:
- Concept similarity (share important technical concepts)
- Knowledge progression (next logical step in learning)
- Difficulty level (similar or slightly more advanced)
- Complementary topics (fills gaps, adds depth)
- Avoid: Same beginner topic repeated, completely unrelated advanced topics
Output format:
\\`\\`\\`json
{
"recommendations": [
{
"slug": "skills-explained",
"title": "How Skills Work in Claude",
"reasoning": "Explains the Skills concept in detail, which this article mentions but doesn't fully cover. Good next step.",
"relevanceScore": 9,
"prerequisiteFor": ["mcp-integration", "advanced-agent-patterns"]
}
]
}
\\`\\`\\`
`;
Execution:
Prepare available articles database:
// Build index of all articles by scanning content/docs/{lang}/{category}
const availableArticles = glob(`content/docs/${lang}/${category}/*.mdx`)
.map(path => {
const content = readFile(path);
const frontmatter = parseFrontmatter(content);
const slug = path.match(/\/([^/]+)\.mdx$/)[1];
return {
slug,
title: frontmatter.title,
description: frontmatter.description,
difficulty: frontmatter.difficulty,
tags: frontmatter.tags,
concepts: loadConcepts(slug, lang) // From archive/concepts/{slug}.json
};
});
Call Claude AI for analysis:
const response = await askClaude(relatedArticlesPrompt);
const { recommendations } = JSON.parse(response);
Validate recommendations:
if (recommendations.length < 3) {
console.warn("โ ๏ธ AI returned only ${recommendations.length} recommendations");
console.warn("โ Expected 3-5 articles for good user experience");
}
if (recommendations.length > 5) {
console.warn("โ ๏ธ Too many recommendations (${recommendations.length}), truncating to 5");
recommendations = recommendations.slice(0, 5);
}
Insert RelatedArticles component in MDX (before Source Declaration):
import { RelatedArticles } from 'fumadocs-ui/components/related-articles';
# ... article content ...
<RelatedArticles
articles={recommendations.map(rec => ({
href: `/${lang}/${category}/${rec.slug}`,
title: rec.title,
description: rec.description || rec.reasoning
}))}
/>
## โน๏ธ Source Information
# ... rest of article ...
Save recommendation metadata:
writeJson(`archive/recommendations/${articleSlug}-${lang}.json`, {
article: articleSlug,
lang: languageCode,
articleTitle: articleTitle,
totalRecommendations: recommendations.length,
recommendations: recommendations.map(rec => ({
...rec,
targetArticlePath: `content/docs/${lang}/${category}/${rec.slug}.mdx`
}))
});
Output summary:
๐ค AI Related Articles Analysis Complete:
โ
Analyzed ${availableArticles.length} available articles
๐ฏ Selected ${recommendations.length} recommendations
๐ Relevance scores: ${recommendations.map(r => `${r.slug}(${r.relevanceScore})`).join(', ')}
๐ก AI reasoning: ${recommendations.map(r => `${r.slug}: ${r.reasoning}`).join('\n ')}
AI Decision Examples:
Example 1: Concept Progression
Current: "Getting Started with MCP"
Available: ["MCP Architecture", "MCP Security", "MCP vs REST", "Skills", "Agents"]
AI Decision:
- โ
Recommend "MCP Architecture" (next logical step)
- โ
Recommend "MCP Security" (important consideration)
- โ Skip "MCP vs REST" (too advanced for beginner article)
- โ Skip "Skills" and "Agents" (different topics)
Reasoning: "Architecture" and "Security" extend MCP knowledge naturally.
Example 2: Avoid Redundancy
Current: "React Hooks - useState Guide"
Available: ["React Hooks - useEffect", "React Context API", "Vue.js Introduction", "React TypeScript"]
AI Decision:
- โ
Recommend "React Hooks - useEffect" (next hook to learn)
- โ
Recommend "React Context API" (solves related problems)
- โ Skip "Vue.js Introduction" (different framework)
- โ Skip "React TypeScript" (different concern - typing)
Reasoning: Focus on React ecosystem, avoid framework comparisons.
Example 3: Prerequisite Chain
Current: "Building Multi-Agent Systems" (Advanced)
Available: ["Agent Basics", "MCP Protocol", "Skills Explained", "Claude API"]
AI Decision:
- โ Skip "Agent Basics" (too basic, assume knowledge)
- โ
Recommend "Skills Explained" (prerequisite pattern)
- โ
Recommend "MCP Protocol" (underlying technology)
- โ Skip "Claude API" (unrelated to agents)
Reasoning: Advanced readers need prerequisite patterns, not basics.
Example 4: Complementary Depth
Current: "Frontend Performance - Bundle Size"
Available: ["Frontend Performance - Caching", "Frontend Performance - Lazy Loading", "React Optimization", "Webpack Configuration"]
AI Decision:
- โ
Recommend "Frontend Performance - Caching" (same series)
- โ
Recommend "Frontend Performance - Lazy Loading" (same series)
- โ Skip "React Optimization" (framework-specific, not core concept)
- โ Skip "Webpack Configuration" (tool-specific, too narrow)
Reasoning: Same conceptual series provides best learning path.
AI vs Rule-Based Comparison:
| Scenario | Rule-Based (Tags/Category) | AI (Understanding) |
|---|---|---|
| Tag "Performance" | Shows 15 articles (all generic) | Picks 3-5 truly relevant by sub-topic |
| Article "React Hooks" | Recommends "Vue.js" (same: frontend) | Recommends "useEffect guide" (React only) |
| Beginner article | Recommends random same-difficulty | Recommends logical progression path |
| Advanced topic | Recommends confusing basics | Recommends prerequisite depth articles |
| Article mentions "MCP" | Recommends all MCP-tagged | Distinguishes MCP architecture vs security |
Key AI Decision Points:
When AI Cannot Recommend:
Quality Thresholds:
Caching Strategy:
archive/recommendations/ (JSON files)User Experience:
CRITICAL: Create proper meta.json files for sidebar navigation with localized titles.
Load reference files:
references/category-translations.json - Get translated category namesreferences/category-icons.json - Get appropriate iconsFor each language (en, zh, fr, ko):
Create/Update category meta.json: content/docs/{lang}/{category}/meta.json
{
"title": "{translated_category_name}",
"icon": "{category_icon}",
"pages": ["{article-slug}", "..."],
"defaultOpen": false
}
Example for ai-ml category:
English (content/docs/en/ai-ml/meta.json):
{
"title": "AI & Machine Learning",
"icon": "Brain",
"pages": ["{article-slug}", "..."],
"defaultOpen": false
}
Chinese (content/docs/zh/ai-ml/meta.json):
{
"title": "AI ไธๆบๅจๅญฆไน ",
"icon": "Brain",
"pages": ["{article-slug}", "..."],
"defaultOpen": false
}
French (content/docs/fr/ai-ml/meta.json):
{
"title": "IA et Apprentissage Automatique",
"icon": "Brain",
"pages": ["{article-slug}", "..."],
"defaultOpen": false
}
Handling existing meta.json:
pages arraypages array exists, insert article slug; if not, create with ["{slug}", "..."]Update/Create root meta.json: content/docs/{lang}/meta.json
pages arrayExample root meta.json:
{
"title": "Documentation",
"pages": [
"index",
"getting-started",
"---[Book]Categories---",
"ai-ml",
"development",
"data",
"..."
]
}
Translation mapping for all 8 categories:
| Category | English | Chinese | French |
|---|---|---|---|
| ai-ml | AI & Machine Learning | AI ไธๆบๅจๅญฆไน | IA et Apprentissage Automatique |
| development | Development | ๅผๅ | Dรฉveloppement |
| data | Data | ๆฐๆฎ | Donnรฉes |
| design | Design | ่ฎพ่ฎก | Design |
| content | Content | ๅ ๅฎน | Contenu |
| business | Business | ๅไธ | Affaires |
| devops | DevOps | DevOps | DevOps |
| security | Security | ๅฎๅ จ | Sรฉcuritรฉ |
Icon mapping for categories:
| Category | Icon | Alternative Icons |
|---|---|---|
| ai-ml | Brain | Cpu, Zap, Sparkles |
| development | Code | Terminal, Braces, FileCode |
| data | Database | BarChart, PieChart, TrendingUp |
| design | Palette | Paintbrush, Layers, Layout |
| content | FileText | BookOpen, Book, FileEdit |
| business | Briefcase | TrendingUp, DollarSign, Users |
| devops | Server | Cloud, Container, GitBranch |
| security | Shield | Lock, ShieldCheck, Key |
Important Notes:
... syntax to auto-include other pages: ["featured-article", "..."]Create archive directory:
mkdir -p "archive/{YYYY-MM}/{article-slug}/images"
Save the following files:
{
"source_url": "{original_url}",
"download_date": "{ISO_8601_timestamp}",
"title": "{original_title}",
"author": "{author}",
"languages": ["en", "zh", "fr"],
"category": "{category}",
"difficulty": "{difficulty}",
"tags": ["{tag1}", "{tag2}", "{tag3}"],
"published_files": {
"en": "content/docs/en/{category}/{slug}.mdx",
"zh": "content/docs/zh/{category}/{slug}.mdx",
"fr": "content/docs/fr/{category}/{slug}.mdx"
},
"images": [
{
"original_url": "{image_url}",
"local_path": "/images/docs/{slug}/{image-name}",
"status": "success|failed"
}
]
}
Provide a comprehensive summary to the user:
โ
Article Import Complete!
๐ Article: {title}
๐ Source: {source_url}
๐ Category: {category}
๐ Difficulty: {difficulty}
๐ท๏ธ Tags: {tag1, tag2, tag3, ...}
๐ Files Created:
โ
en: content/docs/en/{category}/{slug}.mdx
โ
zh: content/docs/zh/{category}/{slug}.mdx
โ
fr: content/docs/fr/{category}/{slug}.mdx
๐ Navigation (meta.json):
โ
en: content/docs/en/{category}/meta.json ("{English Category Name}")
โ
zh: content/docs/zh/{category}/meta.json ("{Chinese Category Name}")
โ
fr: content/docs/fr/{category}/meta.json ("{French Category Name}")
๐ Article added to sidebar navigation
๐จ Icon: {category_icon}
๐ผ๏ธ Images (Smart Filtering & Strategy):
๐ฏ Strategy Used: {image_strategy} (external/download/auto)
โ
Found {total_images} total images on page
๐ฏ Identified {filtered_images} content images (diagrams/screenshots)
๐ซ Skipped {skipped_images} decorative images (placeholders/icons)
{image_strategy_section}
๐จ Article Cover (Step 6.5):
โ
Generated SVG illustration using philosophical-illustrator
๐ Saved to: public/images/docs/{slug}/cover.svg
๐จ Color palette: {palette_name} ({category} category)
๐ Dimensions: 800x450px
๐ Referenced in frontmatter: image: /images/docs/{slug}/cover.svg
โจ Visual theme: {theme_description}
(Or: โ ๏ธ Cover generation skipped/failed - article continues without image)
๐บ Videos:
โ
Detected {video_count} YouTube video(s)
โ
Auto-embedded using iframe component
โญ๏ธ No download needed (video loaded from YouTube)
๐ Content Safety Processing (Step 2.5):
โ
Processed content from: {source_name}
๐ก๏ธ Degraded unknown components: {degraded_component_count}
๐ง Fixed MDX pitfalls: {fixed_pitfall_count}
๐ฆ Injected imports: {injected_import_count}
โ ๏ธ Warnings: {warning_count}
{safety_warnings_details}
๐ค AI-Powered Enhancements:
Cross-References (Step 3.7):
โ
Analyzed {concept_count} technical concepts
โ
Inserted {cross_ref_links} contextual links in content
๐ Positions: {link_positions}
๐ก AI reasoning: {ai_cross_ref_reasoning}
๐ Saved to: archive/links/{slug}.json
Related Articles (Step 8):
โ
Analyzed {available_article_count} articles in {category}/{lang}
๐ฏ Selected {recommendation_count} AI-recommended articles
๐ Relevance scores: {relevance_scores}
๐ก AI selection reasoning: {recommendation_reasoning}
๐ Saved to: archive/recommendations/{slug}-{lang}.json
๐ฏ RelatedArticles component inserted before source section
๐ Source Tracking:
โ
Frontmatter includes full source metadata (url, author, dates, license)
โ
SourceAttribution component added at top
โ
Source declaration added at bottom
๐ฆ Archive: archive/{YYYY-MM}/{slug}/
- original.md
- metadata.json (with complete import info)
- concepts.json (AI-extracted concepts from Step 3.6)
- links.json (cross-reference metadata)
- recommendations/ ({lang-specific recommendation files})
- images/ ({count} files)
โ ๏ธ Issues (if any):
- Failed images: {list_of_failed_urls}
- Skipped images (filtered): {filtered_count}
- meta.json conflicts: {any_merge_issues}
- Warnings: {any_warnings}
๐ Next Steps:
1. Review generated MDX files for accuracy (especially code blocks)
2. Test article in local Fumadocs (npm run dev)
3. Verify SourceAttribution displays correctly at top
4. Check source declaration at bottom
5. Verify images display (open article with diagrams)
6. Test YouTube video embeds (if applicable)
7. Adjust translations if needed
Image Strategy Details (customized based on choice):
If using external URLs (no download):
๐ผ๏ธ Images (External URLs - No Download):
โ
Kept original URLs (CORS supported)
โญ๏ธ No downloads performed
โ
Saved storage space: {total_size_mb}MB
โ
Faster import: skipped download step
๐ Referenced: {filtered_images} external URLs
If downloading to local:
๐ผ๏ธ Images (Downloaded to Local):
โ
Downloaded: {successful_downloads}/{filtered_images}
๐ Location: public/images/docs/{slug}/
๐พ Storage used: {total_size_mb}MB
โ
Offline availability
โ
Full control over files
Key Improvements from v2.0:
๐ค AI-Powered Article Association (NEW):
๐ผ๏ธ Smart image filtering: 80-90% reduction in decorative image downloads
๐ผ๏ธ Flexible image strategies: external/download/auto based on CORS support
๐บ YouTube video auto-detection and embedding
๐ Comprehensive source tracking (frontmatter + components + footer)
โก Faster processing (fewer downloads with external strategy)
๐ฏ Better user experience (auto-processed, no interruptions)
This is the #1 cause of image processing failures. Must check first!
Symptom:
โ CRITICAL ERROR: response.images is undefined!
โ This means 'withAllImages: true' parameter was NOT passed to read_url
โ Image processing will be completely skipped!
Root Cause:
read_url tool defaults to text-only mode (no images)withAllImages: true, the tool returns response.images = undefinedHow to Fix:
Re-run Step 2 with correct parameter:
Tool: read_url
Parameters:
- url: "{article_url}"
- withAllImages: true // โ MUST include this!
Verify the response:
if (!response.images) {
throw new Error("withAllImages parameter missing!");
}
console.log(`โ
Found ${response.images.length} images`);
Then re-run the full import:
Prevention: See Step 2, Sub-step 4 (๐ด CRITICAL VALIDATION) for mandatory check that prevents this error.
Why this happens:
withAllImages: true is easy to overlook< in plain text (e.g., <5, <number, <word)>) don't interfere with JSX< with < in plain text contexts> with > in comparison contexts<5k tokens โ <5k tokens or "less than 5k tokens"<script> in text โ <script> or use code formatting<Tag>...</Tag> or self-close <Tag />)Input URL: https://example.com/react-hooks-guide
Expected Output:
react-hooks-guidedevelopmentintermediatereact, hooks, javascript, frontendpublic/images/docs/react-hooks-guide/Input URL: https://example.com/pandas-data-analysis
Expected Output:
pandas-data-analysisdatabeginnerpython, pandas, data-analysis, tutorialInput URL: https://example.com/transformer-architecture-explained
Expected Output:
transformer-architecture-explainedai-mladvancedai, machine-learning, transformers, nlp, deep-learningAlways refer to references/fumadocs-components.md before adding components.
CRITICAL RULE: โ Never implement custom components. Only use Fumadocs built-in components.
Available components include:
<Cards> and <Card> for card layouts<Callout> for important notes<Tabs> and <Tab> for tabbed content<Steps> and <Step> for step-by-step guides<Files>, <Folder>, <File> for file trees<Accordion> for collapsible content<ImageZoom> for zoomable imagesFollow Fumadocs conventions:
content/docs/{lang}/ (en, zh, fr)content/docs/{lang}/{category}/public/images/docs/{slug}/archive/{YYYY-MM}/{slug}/which curlreferences/fumadocs-components.md for correct syntaxv2.3.0 (2025-11-17): Defensive Content Safety Processing (Phase 1)
<number patterns (e.g., "<5k") โ replaces with HTML entity <<script, <div, etc.) in plain text**็ฒไฝ๏ผ**ๆๅญ โ **็ฒไฝ๏ผ** ๆๅญ)v2.2.0 (2025-11-17): Removed Korean Language Support
v2.1.0 (2025-11-17): AI-Powered Article Association System
v2.0.0 (2025-11-16): Smart Image Filtering + YouTube + Source Tracking + Mandatory Validation
<Video> componentSourceAttribution component (top) + Source declaration (bottom) for full attributionread_url with withAllImages: true for structured image dataresponse.images to prevent silent failuresv1.0.0 (2025-11-15): Initial release