{"id":27215417,"url":"https://github.com/nkohari/apocrypha","last_synced_at":"2025-04-10T04:15:45.700Z","repository":{"id":192579976,"uuid":"618094023","full_name":"nkohari/apocrypha","owner":"nkohari","description":"A simple way to build a website using React and Markdoc","archived":false,"fork":false,"pushed_at":"2024-11-26T18:06:55.000Z","size":238,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T04:15:36.087Z","etag":null,"topics":["markdoc","react","static-site-generator"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nkohari.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-23T18:33:51.000Z","updated_at":"2024-12-24T17:40:34.000Z","dependencies_parsed_at":"2024-11-26T21:18:30.364Z","dependency_job_id":null,"html_url":"https://github.com/nkohari/apocrypha","commit_stats":null,"previous_names":["nkohari/apocrypha"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkohari%2Fapocrypha","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkohari%2Fapocrypha/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkohari%2Fapocrypha/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkohari%2Fapocrypha/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nkohari","download_url":"https://codeload.github.com/nkohari/apocrypha/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248154992,"owners_count":21056544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["markdoc","react","static-site-generator"],"created_at":"2025-04-10T04:15:45.048Z","updated_at":"2025-04-10T04:15:45.670Z","avatar_url":"https://github.com/nkohari.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![apocrypha](https://github.com/nkohari/apocrypha/assets/1576/3f30609e-2b2f-465d-b61e-fe101e370672)\n\n\u003e [Apocrypha](https://elderscrolls.fandom.com/wiki/Apocrypha) is a realm of Oblivion created and ruled over by Hermaeus Mora, the Daedric Prince of Knowledge and Fate. It is an endless library consisting of untitled books with black covers, where all forbidden knowledge can be found, and the crackling towers of learning mingle with archways of despair and confusion.\n\n# Apocrypha\n\n**NOTE!** This documentation is still very much a scribbled work-in-progress -- please bear with me as I improve it!\n\nApocrypha is a plugin for [Vite](https://vite.dev) that lets you build websites with [Markdoc](https://markdoc.dev) and [React](https://react.dev). Given a collection of Markdoc documents, a set of React components, and a set of Markdoc tags, Apocrypha will generate a static website. During development, full hot-module reload (HMR) support is available for both content and code.\n\nFor a full example of a project powered by Apocrypha, take a look at [nate.io](https://github.com/nkohari/nate.io), my personal website.\n\n## Usage\n\nTo start using Apocrypha, first install it using `npm` or your favorite Node package manager:\n\n```\n$ npm i @apocrypha/core\n```\n\nThen plug it into your `vite.config.js`. For example:\n\n```ts\nimport {defineConfig} from 'vite';\nimport react from '@vitejs/plugin-react';\nimport {apocrypha} from '@apocrypha/core';\n\nexport default defineConfig({\n  plugins: [\n    apocrypha({\n      paths: {\n        assets: './media',\n        components: './src/components',\n        content: './content',\n        declarations: './src/markdoc',\n      },\n    }),\n    react(),\n  ],\n});\n```\n\nApocrypha will recursively find all `*.md` files within the `content` path and compile them into JavaScript modules which export the Markdoc AST and the article's metadata. These modules are all written to separate JavaScript files, and are loaded dynamically at runtime when you need them.\n\nYou can get the list of articles at runtime using the `useCatalog` hook. For example:\n\n```ts\nimport {useCatalog} from '@apocrypha/core/catalog';\n\nexport const ArticleList = () =\u003e {\n  const catalog = useCatalog();\n  return (\n    \u003cul\u003e\n      {catalog.map((article) =\u003e (\n        \u003cli key={article.path}\u003e\n          \u003ca href={article.path}\u003e{article.metadata.title}\u003c/a\u003e\n        \u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n};\n```\n\n(The `@apocrypha/core/catalog` module isn't actually part of the Apocrypha library; it's a _virtual module_ which is generated at build time for your project.)\n\nTo display an article's content, you can use the `ArticleContent` component. This is a React component which can asynchronously load the article's module and render its content using the Markdoc React renderer. The `ArticleContent` component is designed to work with [`\u003cSuspense\u003e`](https://react.dev/reference/react/Suspense) boundaries, which you can use to show a loading indicator. Here's an example:\n\n```ts\nimport {ArticleContent} from '@apocrypha/core/catalog';\n\ntype PageProps = {\n  path: string;\n};\n\nexport const Page = ({path}: PageProps) =\u003e (\n  \u003cSuspense fallback=\"Loading...\"\u003e\n    \u003cArticleContent path={path} /\u003e\n  \u003c/Suspense\u003e\n);\n```\n\nThe `ArticleContent` component also supports a `variables` prop, which you can use to pass [variables](https://markdoc.dev/docs/variables) through to your Markdoc content. For example, you could load the currently logged-in user from somewhere, and pass it to the `ArticleContent` component like this:\n\n```ts\nimport {ArticleContent} from '@apocrypha/core/catalog';\n\ntype PageProps = {\n  path: string;\n};\n\nexport const Page = ({path}: PageProps) =\u003e {\n  const user = /* get the currently logged-in user from somewhere */\n\n  return (\n    \u003cSuspense fallback=\"Loading...\"\u003e\n      \u003cArticleContent path={path} variables={{user}} /\u003e\n    \u003c/Suspense\u003e\n  );\n};\n```\n\nThen you can access the variable in your content like this:\n\n```md\nHello, {% $user.name %}! Welcome to my awesome website.\n```\n\n## Metadata\n\nYou can associate metadata with articles by creating metadata plugins. Metadata can come from anywhere, but the most common use is to define it in document frontmatter.\n\nFor example, you can give your articles a `title` property, like this:\n\n```md\n## title: Doctors Hate Him! 10 Secrets to Lose Belly Fat Fast\n\nThis is a really interesting and insightful article, which is defintely not clickbait.\n```\n\nAnd then read it with a metadata plugin like this:\n\n```ts\nimport type {MetadataPluginParams} from '@apocrypha/core';\n\nexport async function getTitle({frontmatter}: MetadataPluginParams) {\n  return {title: frontmatter.title};\n}\n```\n\nThe return values of all of the metadata plugins are merged to create the article's metadata. The metadata is included in the response to the `useArticle` hook. It also supports an optional type parameter, so you can enforce type safety between your metadata plugins and the code that consumes the metadata they generate!\n\n```ts\ntype Metadata = {\n  title: string;\n};\n\ntype PageProps = {\n  path: string;\n};\n\nconst Page = ({path}: PageProps) =\u003e {\n  const article = useArticle\u003cMetadata\u003e(path);\n\n  return \u003ctitle\u003e{article.metadata.title}\u003c/title\u003e;\n};\n```\n\nYou can also do more exotic things with metadata plugins. For example, let's say you want to improve your site's performance by adding `\u003clink rel=preload\u003e` tags for all images on each page. You could write a metadata plugin that walks the document's Markdoc AST and extracts the image URLs:\n\n```ts\nimport {AstWalker, type MetadataPluginparams} from '@apocrypha/core';\n\nexport async function getImages({ast}: MetadataPluginParams) {\n  const images = AstWalker.findTags(ast, 'image').map(\n    (node) =\u003e node.attributes.src,\n  );\n\n  if (images.length \u003e 0) {\n    return {images};\n  }\n}\n```\n\nThen, you could read the `images` array at runtime and add the necessary `\u003clink\u003e` tags.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnkohari%2Fapocrypha","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnkohari%2Fapocrypha","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnkohari%2Fapocrypha/lists"}