---
title: कंटेंट स्रोत
description: >-
  लोकल फ़ाइलों, किसी रिमोट रिपॉज़िटरी, या किसी भी कस्टम बैकएंड से डॉक्स लाएँ — और कई स्रोतों को एक ऐसी स्टैटिक-फ़र्स्ट साइट में मिलाएँ जो बिल्ड टाइम पर पढ़ी जाती है।
---

डिफ़ॉल्ट रूप से Blume `.md`/`.mdx` फ़ाइलों का एक फ़ोल्डर पढ़ता है। **कंटेंट स्रोत** आपको कहीं और से पेज लाने देते हैं — किसी रिमोट रिपॉज़िटरी, किसी CMS, या किसी भी कस्टम बैकएंड से — और कई स्रोतों को एक ही साइट में मिलाने देते हैं। स्रोत बिल्ड टाइम पर पढ़े जाते हैं; Blume स्टैटिक-फ़र्स्ट ही रहता है।

## डिफ़ॉल्ट [#the-default]

बिना किसी कॉन्फ़िगरेशन के, Blume आपके कंटेंट रूट (डिफ़ॉल्ट रूप से `docs`) को एक निहित फ़ाइलसिस्टम स्रोत के रूप में स्कैन करता है। टॉप-लेवल `content.root`/`include`/`exclude` विकल्प पहले की तरह ही काम करते हैं — कुछ भी बदलने की ज़रूरत नहीं।

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: { root: "docs" },
});
```

## कई स्रोत [#multiple-sources]

स्रोतों को जोड़ने के लिए एक `content.sources` ऐरे जोड़ें। हर एंट्री एक वैकल्पिक `prefix` से नेमस्पेस की जाती है, इसलिए उसके रूट `/<prefix>/…` के अंदर नेस्ट होते हैं। जब `sources` मौजूद होता है, तो यह निहित डिफ़ॉल्ट की जगह ले लेता है, इसलिए अपने लोकल डॉक्स के लिए एक `filesystem` एंट्री शामिल करें।

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      // Local docs at the site root
      { type: "filesystem", root: "docs" },

      // Remote MDX from a GitHub repo, mounted under /sdk
      {
        type: "mdx-remote",
        prefix: "sdk",
        github: { owner: "acme", repo: "sdk", ref: "main", path: "docs" },
      },
    ],
  },
});
```

अगर दो स्रोत एक ही रूट पर पहुँचते हैं, तो Blume एक `BLUME_DUPLICATE_ROUTE` बिल्ड एरर रिपोर्ट करता है — हर स्रोत को एक अलग `prefix` दें।

## Obsidian

बिल्ट-इन `obsidian` स्रोत किसी [Obsidian](https://obsidian.md) वॉल्ट को उसी जगह पढ़ता है। कोई एक्सपोर्ट चरण नहीं है और आपके रिपॉज़िटरी में कुछ भी जेनरेट नहीं होता: वॉल्ट ही सत्य का स्रोत बना रहता है, और Blume लोड करते समय Obsidian की बोली को Markdown में उतार देता है।

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "obsidian",
        prefix: "notes",
        vault: "vault",
        // Vault folder names to skip at any depth, on top of dot-folders
        exclude: ["Templates", "Daily"],
      },
    ],
  },
});
```

`[[Wikilinks]]` रूट लिंक बन जाते हैं, जिन्हें पथ के बजाय पूरे वॉल्ट में नोट के नाम से संबोधित किया जाता है — ठीक उसी तरह जैसे Obsidian नोट्स को संबोधित करता है। कस्टम लिंक टेक्स्ट (`[[Note|label]]`), शीर्षक ऐंकर (`[[Note#Install]]`), पूरे पथ (`[[folder/Note]]` और `[[folder/Note.md]]`), वे आंशिक पथ जो Obsidian की डिफ़ॉल्ट "shortest path when possible" सेटिंग लिखती है (`[[guides/Note]]`), और `[[Note\|label]]` रूप जो Obsidian किसी टेबल सेल के अंदर लिखता है — ये सभी काम करते हैं, और जो नोट अपने फ़्रंटमैटर में `slug` सेट करता है, उसे उस रूट पर लिंक किया जाता है जिस पर वह slug प्रकाशित होता है। जब दो नोट्स का नाम एक ही होता है, तो वह नोट जीतता है जिसका पूरा वॉल्ट पथ ठीक वही नाम है — Obsidian किसी लिंक को नाम से पहले पथ के रूप में हल करता है — उसके बाद वॉल्ट क्रम में पहला (नोट्स से पहले फ़ोल्डर, केस-असंवेदनशील रूप से, Obsidian के फ़ाइल एक्सप्लोरर की तरह)। Blume केवल तभी चेतावनी देता है जब कोई विकिलिंक वास्तव में ऐसी टकराहट से होकर हल होता है; अस्पष्टता दूर करने के लिए एक लंबा पथ लिखें। कोई ब्लॉक रेफ़रेंस (`[[Note#^id]]`) अपने नोट से बिना ऐंकर के लिंक होता है: ब्लॉक बिना किसी ऐसी id के रेंडर होते हैं जिस पर पहुँचा जा सके। कोई शीर्षक ऐंकर लक्ष्य नोट के वास्तविक शीर्षकों के सापेक्ष हल होता है, जिनका मिलान उसी तरह किया जाता है जैसे Obsidian का ऑटोकम्प्लीट उन्हें लिखता है (`**bold**`, `` `code` `` और लिंक सिंटैक्स हटाकर) और जिन्हें उसी `extractHeadings` पास द्वारा slug किया जाता है जो पेज मैनिफ़ेस्ट भरता है — इसलिए `#Install` का लिंक शीर्षक पर पहुँचता है, न कि किसी ऐसी id पर जो कोई पेज उत्सर्जित ही नहीं करता। `[[#Install]]` उस नोट के किसी शीर्षक को संबोधित करता है जिसे आप लिख रहे हैं। किसी ऐसे शीर्षक का लिंक जो मौजूद नहीं है, पेज लिंक बनाए रखता है, ऐंकर हटा देता है, और चेतावनी देता है।

फ़्रंटमैटर वही रखता है जो Blume का [पेज स्कीमा](/docs/reference/frontmatter) स्वीकार करता है, साथ ही वह हर कुंजी जिसे आप [`frontmatter.extend`](/docs/reference/frontmatter#custom-keys) में घोषित करते हैं (या, उस `type` के नोट्स के लिए, किसी कंटेंट टाइप का `frontmatter`); हर दूसरी Obsidian प्रॉपर्टी — Dataview फ़ील्ड्स, Templater तिथियाँ, `publish`, और Obsidian की अपनी `tags`, `aliases` और `cssclasses` — नोट के उतारे जाने पर हटा दी जाती है, इसलिए Properties UI से लिखा गया वॉल्ट बिना फ़्रंटमैटर एरर के बिल्ड होता है। `aliases` को हल करने के बजाय हटा दिया जाता है — उपनाम लिंक लक्ष्य अभी समर्थित नहीं हैं। किसी नोट के बगल में मौजूद कोई सापेक्ष Markdown इमेज (`![chart](./chart.png)`) वॉल्ट से परोसी जाती है, और जब वॉल्ट आपकी git रिपॉज़िटरी के अंदर रहता है, तो वॉल्ट पेजों को किसी भी अन्य पेज की तरह git से प्राप्त ["Last updated" तिथियाँ](/docs/configuration#last-modified) मिलती हैं। "Edit this page" लिंक `github.dir` के ज़रिए हल होते हैं, इसलिए किसी मोनोरेपो में डॉक्स ऐप के बगल में बैठा वॉल्ट भी अपनी फ़ाइल से लिंक होता है; रिपॉज़िटरी के बाहर मौजूद वॉल्ट को कोई लिंक नहीं मिलता।

वॉल्ट के अंदर लोकेल डायरेक्टरियाँ और वर्ज़न स्नैपशॉट उसी तरह पढ़े जाते हैं जैसे फ़ाइलसिस्टम स्रोत उन्हें पढ़ता है: [i18n](/docs/content/i18n) कॉन्फ़िगर होने पर `fr/Note.md` `/fr/` के अंतर्गत प्रकाशित होता है, [वर्ज़न](/docs/content/versioning) के साथ `v1.0/Note.md` `/v1.0/` के अंतर्गत, और उन नोट्स के विकिलिंक उस रूट की ओर इशारा करते हैं जिस पर हर एक प्रकाशित होता है।

:::note
जिस शीर्षक में स्वयं कोई लिंक होता है, उसका मैनिफ़ेस्ट ऐंकर शीर्षक के Markdown से आता है और उसका रेंडर किया गया `id` उसके टेक्स्ट कंटेंट से। उस शीर्षक के लिए ये दोनों भिन्न होते हैं, इसलिए उसका कोई विकिलिंक सेक्शन के बजाय पेज पर पहुँच सकता है।
:::

किसी `index` नोट का लिंक किसी काल्पनिक `/index` के बजाय उसके फ़ोल्डर के रूट पर पहुँचता है। **कोई अनसुलझा विकिलिंक बिल्ड को विफल करने के बजाय एक बिल्ड चेतावनी के साथ सादे टेक्स्ट तक सीमित हो जाता है**, इसलिए रीफ़ैक्टर के बीच का वॉल्ट भी प्रकाशित होता रहता है। एकल-पंक्ति `%%comments%%` हटा दिए जाते हैं, किसी HTML कमेंट के अंदर का विकिलिंक (`<!-- [[Draft]] -->`) वैसा ही छोड़ दिया जाता है क्योंकि Obsidian भी उसे छिपाता है, और जिस नोट के फ़्रंटमैटर में `title` नहीं होता उसका शीर्षक उसकी फ़ाइल के नाम से बनता है — वही नियम जो Obsidian स्वयं लागू करता है। `index` नोट एकमात्र अपवाद है: यह किसी नोट के बजाय किसी रूट का नाम बताता है, इसलिए उसका शीर्षक Blume की सामान्य व्युत्पत्ति पर चला जाता है (पहला शीर्षक, फिर मानवीकृत सेगमेंट)। फ़ेंस्ड, इंडेंटेड और इनलाइन कोड हूबहू पास हो जाता है, इसलिए इस सिंटैक्स का दस्तावेज़ीकरण करने वाला नोट बच जाता है।

डॉट-फ़ोल्डर छोड़ दिए जाते हैं, जिनमें Obsidian की अपनी `.obsidian` कॉन्फ़िग डायरेक्टरी और `.trash` शामिल हैं — जिन्हें डेव वॉचर भी अनदेखा करता है, इसलिए ऐप में कोई पैन हिलाने या किसी नोट को ट्रैश में डालने से आपकी साइट दोबारा बिल्ड नहीं होती। किसी नोट को संपादित करने से होती है। वे डायरेक्टरियाँ जिन्हें कोई भी कंटेंट स्कैन नहीं पढ़ता (`node_modules`, `dist`, `.git`, …) भी छोड़ दी जाती हैं, इसलिए प्रोजेक्ट में ही रूट किया गया वॉल्ट डिपेंडेंसी README प्रकाशित नहीं करता। वॉल्ट के अंदर के सिमलिंक का अनुसरण किया जाता है, उसी तरह जैसे फ़ाइलसिस्टम स्रोत उनका अनुसरण करता है, इसलिए वॉल्ट में लिंक किया गया कोई शेयर्ड फ़ोल्डर उसके साथ प्रकाशित होता है। `content.root` के अंदर बैठे वॉल्ट को फ़ाइलसिस्टम स्रोत से बाहर रखा जाना चाहिए (`exclude: ["vault/**"]`); तब `blume version cut` उसे स्नैपशॉट से बाहर छोड़ देता है, क्योंकि वॉल्ट अपने नोट्स को वर्तमान के रूप में प्रकाशित करता रहता है।

अभी तक नहीं उतारा गया: कॉलआउट (`> [!note]`) सादे ब्लॉककोट के रूप में रेंडर होते हैं, एम्बेड (`![[image.png]]`) अछूते पास हो जाते हैं, बहु-पंक्ति `%%comments%%` जहाँ के तहाँ छोड़ दिए जाते हैं, और कोई बैकलिंक ग्राफ़ नहीं है।

## रिमोट MDX [#remote-mdx]

बिल्ट-इन `mdx-remote` स्रोत HTTP पर रॉ `.md`/`.mdx` फ़ेच करता है। फ़ाइलों की सूची या तो किसी GitHub रिपॉज़िटरी सबट्री (`github`) से बनाएँ या किसी रॉ बेस URL के सापेक्ष स्पष्ट रूप से (`url` + `files`):

```ts blume.config.ts
{
  type: "mdx-remote",
  prefix: "sdk",
  url: "https://raw.githubusercontent.com/acme/sdk/main/docs",
  files: ["intro.mdx", "guide.mdx"],
}
```

किसी प्राइवेट रिपॉज़िटरी का टोकन `GITHUB_TOKEN` एनवायरनमेंट वेरिएबल से पढ़ा जाता है — यह कभी भी आपके कॉन्फ़िग या जेनरेट किए गए आउटपुट में इनलाइन नहीं किया जाता, और यह केवल GitHub के अपने होस्ट (`api.github.com`, `raw.githubusercontent.com`) पर ही भेजा जाता है, किसी कस्टम `url` बेस पर कभी नहीं।

रिमोट पेज पूरी MDX-प्लस-कंपोनेंट सटीकता के साथ रेंडर होते हैं: उनकी बॉडी एक छिपी हुई स्टेजिंग डायरेक्टरी में मटीरियलाइज़ की जाती है और आपके लोकल डॉक्स के साथ Astro के ज़रिए रेंडर की जाती है, इसलिए कॉलआउट, टैब और हर दूसरा Blume कंपोनेंट काम करता रहता है।

### कैशिंग और ऑफ़लाइन बिल्ड [#caching-and-offline-builds]

हर रिमोट स्रोत `.blume/cache/<source>/` के अंतर्गत एक स्नैपशॉट रखता है। अगर कोई फ़ेच विफल हो जाता है — कोई नेटवर्क गड़बड़ी या CMS आउटेज — तो Blume बिल्ड को विफल करने के बजाय चेतावनी के साथ आखिरी ज्ञात-अच्छा स्नैपशॉट परोसता है। कैश `.blume/` के अंदर रहता है और फिर से जेनरेट होता है, कभी कमिट नहीं किया जाता।

डेव में, रिमोट कंटेंट एक बार फ़ेच किया जाता है और सेशन के लिए फ़्रीज़ कर दिया जाता है; इसे रीफ़्रेश करने के लिए डेव सर्वर को पुनः आरंभ करें। लोकल फ़ाइलसिस्टम स्रोत हमेशा की तरह हॉट-रीलोड होते हैं। इसके बजाय किसी रिमोट स्रोत में बदलावों के लिए पोल करने के लिए, उस पर `pollInterval` (सेकंड में) सेट करें — डेव सर्वर उस अंतराल पर फिर से फ़ेच करता है और तभी रीलोड करता है जब कंटेंट वास्तव में बदलता है। काम करते समय API पर बार-बार अनुरोध भेजने से बचने के लिए इसे अनसेट छोड़ दें।

## GitHub रिलीज़ [#github-releases]

बिल्ट-इन `github-releases` स्रोत किसी रिपॉज़िटरी की रिलीज़ को एक चेंजलॉग में बदल देता है: हर रिलीज़ एक `type: changelog` एंट्री बन जाती है, इसलिए आपके रिलीज़ नोट्स _ही_ आपका चेंजलॉग हैं — दो बार लिखने की ज़रूरत नहीं। जेनरेट की गई [चेंजलॉग टाइमलाइन](/docs/advanced/changelog) के साथ मिलकर, एक GitHub रिलीज़ प्रकाशित करना एक चेंजलॉग एंट्री भेज देता है।

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "content" },
      {
        type: "github-releases",
        prefix: "changelog",
        owner: "acme",
        repo: "sdk",
        // prereleases: false,  // include prereleases (default off)
        // drafts: false,       // include drafts (needs a write token)
        // limit: 100,          // cap releases, newest-first
      },
    ],
  },
});
```

हर रिलीज़ अपने आप चेंजलॉग फ़ील्ड्स से मैप हो जाती है: उसका नाम (या टैग) शीर्षक बन जाता है, उसकी प्रकाशन तिथि टाइमलाइन क्रम तय करती है, टैग `changelog.version` बन जाता है, और प्रीरिलीज़ को `Prerelease` टैग किया जाता है (बाकी को `Release`)। नोट्स एंट्री की बॉडी के रूप में रेंडर होते हैं। स्रोत को एक `prefix` दें ताकि उसके रिलीज़ पेज `/changelog/v1-2-0` जैसे रूट के अंदर नेस्ट हों।

एक प्राइवेट रिपॉज़िटरी `GITHUB_TOKEN` एनवायरनमेंट वेरिएबल से प्रमाणित होती है — वही टोकन जो अन्य GitHub फ़ीचर इस्तेमाल करते हैं, कभी भी आपके कॉन्फ़िग में इनलाइन नहीं किया जाता। हर रिमोट स्रोत की तरह यह भी `.blume/cache/<source>/` के अंतर्गत कैश होता है और API अनुपलब्ध होने पर ऑफ़लाइन परोसा जाता है। चूँकि चेंजलॉग पूरक होता है, इसलिए बिना कैश के फ़ेच विफलता (मान लीजिए बिना टोकन वाला कोई CI बिल्ड) बिल्ड को विफल करने के बजाय चेतावनी के साथ एक खाली चेंजलॉग तक सीमित हो जाती है — इसे भरने के लिए अपने CI और डिप्लॉय एनवायरनमेंट में `GITHUB_TOKEN` सेट करें।

## Sanity

बिल्ट-इन `sanity` स्रोत एक GROQ क्वेरी चलाता है और हर डॉक्युमेंट के फ़ील्ड्स को फ़्रंटमैटर से तथा उसकी Portable Text बॉडी को Markdown से मैप करता है। `@sanity/client` पैकेज एक वैकल्पिक पीयर डिपेंडेंसी है — इसे तभी इंस्टॉल करें जब आप इस स्रोत का उपयोग करें।

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "sanity",
        prefix: "guides",
        projectId: "abc123",
        dataset: "production",
        query: `*[_type == "guide"]`,
        // Field paths default to title / slug.current / body / _updatedAt
        fields: { slug: "slug.current", body: "content" },
      },
    ],
  },
});
```

किसी प्राइवेट डेटासेट के लिए रीड टोकन `SANITY_TOKEN` एनवायरनमेंट वेरिएबल से आता है। कस्टम Portable Text ब्लॉक टाइप एडैप्टर के `serializers` विकल्प के ज़रिए Blume कंपोनेंट्स से मैप होते हैं, जो तब उपलब्ध होता है जब आप [कस्टम स्रोत](#custom-sources) के माध्यम से सीधे `sanitySource` बनाते हैं।

## Notion

बिल्ट-इन `notion` स्रोत किसी Notion डेटाबेस को एक कलेक्शन में बदल देता है: हर पंक्ति एक पेज बन जाती है, उसकी प्रॉपर्टीज़ फ़्रंटमैटर बन जाती हैं, और उसका ब्लॉक ट्री MDX बन जाता है। कॉलआउट, टॉगल, कॉलम और कोड ब्लॉक संबंधित Blume कंपोनेंट्स से मैप होते हैं। वीडियो ब्लॉक तब एक `<YouTube>` एम्बेड बन जाते हैं जब उनमें कोई YouTube लिंक होता है, अन्यथा एक `<video>` प्लेयर, और दोनों ही स्थितियों में ब्लॉक का कैप्शन एक `<Frame>` कैप्शन बन जाता है; किसी मीडिया फ़ाइल के बजाय किसी वीडियो पेज का लिंक (मान लीजिए कोई Vimeo या Loom URL) एम्बेड किए जाने के बजाय एक चेतावनी के रूप में रिपोर्ट किया जाता है। `@notionhq/client` (v5 या उसके बाद का) एक वैकल्पिक पीयर डिपेंडेंसी है; Blume डेटाबेस को उसके पहले डेटा स्रोत के ज़रिए पढ़ता है।

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "notion",
        prefix: "handbook",
        database: process.env.NOTION_DB_ID,
        // Property names default to the title-typed prop / Description / Slug / Order
        // Set publishedValue to treat Status as a publish gate (opt-in)
        publishedValue: "Published",
      },
    ],
  },
});
```

इंटीग्रेशन टोकन `NOTION_TOKEN` एनवायरनमेंट वेरिएबल से आता है (डेटाबेस को अपने इंटीग्रेशन के साथ शेयर करें)। डिफ़ॉल्ट रूप से हर पेज इंपोर्ट होता है; `Status` प्रॉपर्टी को एक पब्लिश गेट बनाने के लिए `publishedValue` सेट करें — तब कोई भी दूसरा मान `draft: true` से मैप होता है, जिसे प्रोडक्शन बिल्ड हटा देते हैं। **Notion इमेज और वीडियो URL साइन किए हुए होते हैं और समाप्त हो जाते हैं**, इसलिए एडैप्टर उन्हें बिल्ड टाइम पर साइट की एसेट्स में डाउनलोड कर लेता है और रेफ़रेंस दोबारा लिख देता है — कोई CMS एसेट कभी किसी स्टैटिक बिल्ड को खराब नहीं करती। API कॉल एक छोटे रिक्वेस्ट पूल के ज़रिए नियंत्रित गति से किए जाते हैं (एक बार में 3, जो Notion की प्रति-इंटीग्रेशन रेट लिमिट से मेल खाता है) ताकि सैकड़ों पेज वाले डेटाबेस `429` प्रतिक्रियाओं में फँसे बिना इंपोर्ट हो जाएँ; इसे ट्यून करने के लिए स्रोत पर `concurrency` सेट करें।

## प्रीव्यू और सिंक [#preview-and-sync]

दो फ़्लैग यह नियंत्रित करते हैं कि रिमोट कंटेंट कैसे फ़ेच किया जाता है और क्या-क्या शामिल होता है:

- **`--preview`** — `blume dev` या `blume build` पर यह ड्राफ़्ट रेंडर करता है और अप्रकाशित CMS कंटेंट खींचता है — Sanity अपने `previewDrafts` परिप्रेक्ष्य पर स्विच कर जाता है, और Notion `Status` के आधार पर फ़िल्टर करना बंद कर देता है। इस फ़्लैग के बिना प्रोडक्शन बिल्ड हमेशा की तरह ड्राफ़्ट को बाहर रखते हैं, इसलिए प्रीव्यू बिल्ड अप्रकाशित काम को शिप होने से पहले समीक्षा करने का एक सुरक्षित तरीका है।
- **`blume sync`** हर रिमोट स्रोत को फिर से फ़ेच करता है और रनटाइम को दोबारा जेनरेट करता है। डेव कैश-फ़र्स्ट है — कोई रिमोट स्रोत एक बार फ़ेच किया जाता है और पुनः आरंभ पर `.blume/cache` से परोसा जाता है (तेज़ और ऑफ़लाइन-सहिष्णु), इसलिए `blume sync` वह तरीका है जिससे आप डेव सर्वर को पुनः आरंभ किए बिना नवीनतम CMS कंटेंट खींचते हैं (चल रहा सर्वर हॉट-रीलोड करता है)। पहले कैश हटाने के लिए `--force` जोड़ें, या अपने आप रीफ़्रेश करने के लिए किसी स्रोत पर `pollInterval` सेट करें।

```sh
blume dev --preview      # author workflow: see drafts live
blume build --preview    # render a full preview build
blume sync               # refresh remote content now
blume sync --force       # ...ignoring any cached snapshot
```

## कस्टम स्रोत [#custom-sources]

`ContentSource` इंटरफ़ेस को लागू करने वाला कोई भी ऑब्जेक्ट सीधे पास किया जा सकता है, और इसी तरह कस्टम सीरियलाइज़र वाला कोई एडैप्टर — या कोई भी ऐसा बैकएंड जो बिल्ट-इन नहीं है — बिना अपने SDK को कोर इंस्टॉल तक पहुँचाए जुड़ जाता है:

```ts blume.config.ts
import { defineConfig } from "blume";
import { sanitySource } from "blume/sources/sanity.ts";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "custom",
        source: sanitySource({
          name: "guides",
          prefix: "guides",
          projectId: "abc123",
          dataset: "production",
          query: `*[_type == "guide"]`,
          // Map custom Portable Text blocks to Blume components
          serializers: {
            callout: (block) => `<Callout>${block.text}</Callout>`,
          },
        }),
      },
    ],
  },
});
```

एक स्रोत अपने नेटिव आकार (Portable Text, Notion ब्लॉक, रिमोट HTML) को Markdown/MDX टेक्स्ट में नॉर्मलाइज़ कर देता है, इसलिए पेज चाहे कहीं से भी आए, वही कंपोनेंट और मार्कडाउन फ़ीचर लागू होते हैं।

लोकल फ़ाइलें पढ़ने वाले किसी कस्टम स्रोत को हर एंट्री पर `sourcePath` और स्वयं स्रोत पर `contentRoot` सेट करना चाहिए। `sourcePath` डायग्नोस्टिक्स में फ़ाइल का नाम बताता है और उसके बगल की सापेक्ष इमेज हल करता है; `contentRoot` उस git `log` की सीमा तय करता है जो पेजों को तिथि देता है, इसलिए इसके बिना स्रोत के पेजों को git से प्राप्त कोई ["Last updated" तिथि](/docs/configuration#last-modified) नहीं मिलती।
