Vision
If I had to describe what this is about in one sentence, I would say it is a database for your content. However, this doesn’t really help to grasp the whole concept. Let’s take a closer look.
Content Layer
The content layer exists in all static site generators (one way or another). Basically:
- There is a folder with content (Markdown, JSON, YAML, images, etc.)
- There is a function to get a list of all entries. It can also sort, filter, and paginate.
- There is a function to get one entry. It can parse content (frontmatter, Markdown) and render it (to HTML, for example).
- Often, there is a caching layer and a reactive interface.
Let’s see some examples.
Hugo
- Folder with content: hardcoded to
content
- List of all entries:
.Site.Pages
- To sort:
.Site.Pages.ByTitle
- To paginate:
.Paginate collection pageNumber
- To filter:
.Site.RegularPages.ByTitle param value
- To filter:
.Resources.ByType value
,.Resources.GetMatch value
- To sort:
- One entry:
- Data:
- HTML:
.Content
- Git metadata:
.GitInfo
- Frontmatter:
.Params.value
- HTML:
Astro: Content Collections
- Folder with content: hardcoded to
src/content
- List of all entries:
await getCollection(collection);
- To filter:
await getCollection(collection, ({ data }) => {... });
- To sort:
.sort((a,b) => { ... })
(standard JS) - To paginate:
paginate(collection, { pageSize: 2 })
- To filter:
- One entry:
const entry = await getEntry(collection, slug);
- Data:
const { Content, headings } = await entry.render();
- HTML:
<Content />
- Frontmatter:
entry.data
- HTML:
Compared to Hugo, we can also specify a schema for the content:
Contentlayer
- Folder with content: defined in
defineDocumentType
- List of all entries:
allItems
- To filter:
allItems.filter(x => {...})
(standard JS) - To sort:
allItems.sort((a, b) => {...})
(standard JS)
- To filter:
- One entry:
allItems.find
(standard JS, I believe)
We can also define a schema:
Other
Content Graph
Articles (Markdown files) plus hyperlinks ([some](/thing)
) form a graph. Some solutions allow us to treat content as a graph:
- Link resolution: wiki-links, portable markdown links
- Backlinks
- Visualize content as a graph
- Detect broken links
Obsidian
Quartz
Other
- Detect broken links: remark-lint-no-dead-urls, mdv, markdown-link-check, remark-validate-links
- Visualize content as a graph: markdown-links, markmap.js, dundalek/markmap
- Markdown links: obsidian-export
Query Interface
The content layer already exposes some basic query interfaces. However, there are solutions that take this idea further by exposing a query interface as a query language.
The most notable solutions in this area are: docsql and obsidian-dataview. They allow users to use a SQL-like language to query the content.
Other options include using some kind of faceted search interface or employing a graph-query language, like Cypher or Datalog.
Core
The main disadvantage of all solutions mentioned above (except for Contentlayer
) is that they are built into other applications and are not reusable. I believe it would be beneficial to implement a core library that could later be reused for:
- Content layer for Astro (or Next.js, Nuxt, etc.)
- Language Server (LSP)
- CLI to transform Markdown files, for example, from an Obsidian vault to Hugo format
- A second-brain note-taking app, like Obsidian or Foam