Are you intrigued by networked note-taking apps?
Do you want to build "blazing" fast sites on the JAMStack?
Have you heard about the digital garden craze sweeping the nation and want to make one of your own?
Maybe Obsidian + Gatsby will be as good to you as they have been to me.
In addition to being a great note-taking tool, Obsidian functions as an excellent content manager. When combined with a git-based deployment solution like Netlify (and a few plugins), it compares favorably to other git-based CMS's such as Forestry and Netlify CMS, with the added benefit of backlinks, graph views, and a bunch of bells and whistles.
Open a new Obsidian vault in your content
folder, .gitignore
the config folder and its CSS, add frontmatter templates to your Obsidian settings, and voila! You've got a slick new CMS.
Add MDX to Gatsby and you can pass shortcodes through your Obsidian files too. This allows you to display interactive, custom React components in your notes. If this sounds fun to you, I've cataloged the steps I took to set it up below.
I have made the repo for this demo public. You can find it here.
Getting Started
Note: I am a beginner developer and this is a beginner-friendly tutorial. If y'all spot anything that could be improved upon, please let me know!
We will start with the totally empty gatsby-hello-world
starter to keep things simple.
Navigate to the folder where you plan to house your site and enter
gatsby new obsidian-cms-demo https://github.com/gatsbyjs/gatsby-starter-hello-world.git`
Once the site is booted up let's install some dependencies. This set up will depend on gatsby-source-filesystem
and gatsby-plugin-mdx
.
Navigate to your new project directory, then install them at the command line:
npm i gatsby-source-filesystem
npm i gatsby-plugin-mdx
Configure plugins
Add both plugins to gatsby-config.js
. Make sure the MDX plugin reads markdown files as .mdx as well, since .md files are what Obsidian creates. Tell gatsby-source-filesystem
to read a folder notes
that is inside a folder named content
:
//gatsby-config.js
...
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.mdx`, `.md`],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `notes`,
path: `${__dirname}/content/notes/`,
},
},
Setting up Obsidian
Make a folder in the root of your project named content
.
mkdir ./content
Then, after downloading and installing Obsidian, open that folder as a pre-existing vault.
Navigate to your project folder and open content
.
You may wish to configure this differently, but I created a second folder within content called notes
. This way, all content you want published to your website is automatically kept apart from the Obsidian config files.
Create your first note! We will test out our page creation file with it soon.
You'll notice a new folder, .obsidian
, was added to the content folder. We can tell git to ignore it. We will also be adding a frontmatter template to Obsidian soon, so I've created a _private
folder at content/_private
to house it (as well as any drafts and journals you may want in the future). If you install any custom CSS to your vault, you can add those to the .gitignore too.
In the .gitignore
file:
# Obsidian Files
/content/.obsidian
/content/_private
# Optional custom CSS
obsidian.css
Now, gatsby-source-filesystem
will only read content in the notes
folder, and will not push any other files to your repo. You can write and setup Obsidian in peace.
Creating pages using gatsby-node.js
We now need a way to programmatically create pages from the files Obsidian creates.
There's a lot of ground to cover here and I may gloss over it, but I'm getting the code from the official docs at: Creating Pages from Data Programmatically | Gatsby. It is a great walkthrough and there are plenty like it on the Gatsby docs.
First, in gatsby-node.js
:
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions
const notesTemplate = require.resolve(`./src/templates/noteTemplate.js`)
const result = await graphql(`
{
allFile {
edges {
node {
childMdx {
slug
}
}
}
}
}
`)
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result.data.allFile.edges.forEach(({ node }) => {
createPage({
path: `notes/${node.childMdx.slug}`,
component: notesTemplate,
context: {
// additional data can be passed via context
slug: node.childMdx.slug,
},
})
})
}
Note that the GraphQL query is set to query allFiles
instead of allMdx
. There may be a better solution than this, but I set it up in case I wanted to create pages via other sources in the future (GraphQL has a handy piece of data called sourceInstanceName
that can help you sort different sources, available from allFile
.)
We've also specified a noteTemplate file for all incoming notes, so let's make that now.
Create a new folder in src
called templates
, then add noteTemplate.js
to the templates folder.
This is a very barebones template and you will probably want to add a layout component and styles to it as well. I've added a link to go Back Home for easier navigation.
//in src/templates/noteTemplate.js
import React from "react"
import { graphql, Link } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
export default function noteTemplate({ data }) {
const { mdx } = data
return (
<article>
<MDXRenderer>{mdx.body}</MDXRenderer>
<Link to="/">Back Home</Link>
</article>
)
}
export const query = graphql`
query($slug: String!) {
mdx(slug: { eq: $slug }) {
body
}
}
`
We import the MDXRenderer
component to display the body of our note as an MDX file. In the GraphQL query, we're taking in the variable that we passed at the bottom of gatsby-node.js
in the context:
section.
Setting up a Basic Frontmatter Template in Obsidian
We will probably want an easy way to grab a note's title and date of creation. Thankfully, this is simple and relatively frictionless using Obsidian's template plugin. In your vault, navigate to Settings/Plugin and turn on the Template plugin. Specify a _templates
folder inside _private
, then create a file called "Frontmatter" in it with the following:
---
title: "{{title}}"
date: {{date}}T{{time}}
---
Obsidian will automatically fill in the title and date values every time you call the template. Hook the template up to a hotkey and it can be very fluid to create new notes with formatted frontmatter.
Note: I struggled to find the best way to format the date. I found that creating it in the style above allows notes to be sorted by date, and formatting the date to be more readable (either via the template or GraphQL) gave me troubles when I tried to sort. So instead, I imported Day.js in pages where the date was displayed and it worked without trouble.
After inserting the template at the top of the page, our Hello, World note now looks like this:
---
title: Hello world
date: 2020-10-14T13:22
---
This is the first note in my Gatsby digital garden.
Showing All Notes on the Index Page
Just to illustrate the idea, we'll make a list of all note pages on our home page.
We can easily accomplish this with a GraphQL query for each note's title, date, and slug.
In pages/index.js
:
import React from "react"
import { Link, graphql } from "gatsby"
const Home = ({
data: {
allMdx: { edges },
},
}) => {
const Notes = edges.map(edge => (
<article>
<Link to={`/notes/${edge.node.slug}`}>
<h1>{edge.node.frontmatter.title}</h1>
</Link>
<p>{edge.node.frontmatter.date}</p>
</article>
))
return <section>{Notes}</section>
}
export default Home
export const pageQuery = graphql`
query MyQuery {
allMdx {
edges {
node {
slug
frontmatter {
title
date
}
}
}
}
}
`
To walk through what we've done here:
- We pass the edges of the query into our page's function, which allows us to retrieve the data in our markup
- We take the array of edges and use the
.map()
array method to create a new array of markup with link to each note page, displaying its title and (ugly-formatted) date (I recommend fixing this with Day.js) - We pass this new array
Notes
to the JSX that's returned by the function - We export the Home page function
- We export the GraphQL query that retrieves our data
Now fire up the dev server using gatsby develop
, and you should see your first note displayed there!
Add Wikilink and Reference Plugins
Wikilinks and Gatsby-plugin-catch-links
Right now our site is pretty unimpressive and its functionality is more or less the same as any old markdown blog with posts written in the IDE. We will fix that!
Two fundamental features of networked note software are
- support for
[[wikilink]]
syntax - support for linked references
and we have some simple plugins that can accomplish both of these things!
Thanks to the excellent work of developer Mathieu Dutour on his gatsby-digital-garden, we can easily get both of these features running.
We will use two items in his packages
directory: gatsby-remark-double-brackets-link
and gatsby-transformer-markdown-references
.
First, let's install them in our project:
npm i gatsby-remark-double-brackets-link
npm i gatsby-transformer-markdown-references
We can now configure the plugins in our gatsby-config.js
. First let's set up the double brackets link. It will be configured inside the MDX plugin:
//in gatsby-config.js
plugins: [
...
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.mdx`, `.md`],
gatsbyRemarkPlugins: [
{
resolve: "gatsby-remark-double-brackets-link",
options: {
titleToURLPath: `${__dirname}/resolve-url.js`,
stripBrackets: true,
},
},
]
},
},
]
Both of these options are... optional. Because our note pages are created at index/notes/note-slug, we need a way to tell the autogenerated wikilinks to follow that same convention.
(I did this because I set up a blog in addition to a garden on my personal site, and I think it's good practice to separate the note files from any additional main-level pages as well.)
At the root of the project, create a file named resolve-url.js
. The code here is quite simple:
const slugify = require("slugify")
module.exports = title => `/notes/${slugify(title)}`
There you have it! Now any [[double bracket link]]
in our MDX notes will automatically turn into a link to another note page.
If you care to try it out, create a new wikilink in your first note. Open the link in Obsidian by Ctrl + clicking it and add its frontmatter via the template.
Make sure your new note is inside the notes
folder. You can configure where new files are stored in the Vault settings.
Boot up the dev server again and you should see the link in the Hello World note. Click it and you'll be routed to your new note.
You may notice something wrong, though-- the link takes a long time to load. Isn't lightning-fast linking one of the core features of Gatsby? Yes it is and there's a very easy plugin solution in gatsby-plugin-catch-links
.
Install with npm i gatsby-plugin-catch-links
, and throw it in your gatsby-config.js
file:
//gatsby-config.js
plugins: [
...
`gatsby-plugin-catch-links`,
...
Now the next time you click the link it should be "blazing".
Markdown References
After installing gatsby-transformer-markdown-references
, add it to the root level of your gatsby-config (ie, not inside of the gatsby-mdx plugin).
//gatsby-config.js
plugins: [
...
{
resolve: `gatsby-transformer-markdown-references`,
options: {
types: ["Mdx"], // or ['RemarkMarkdown'] (or both)
},
},
]
Now if you check out GraphiQL, the super-handy GraphQL tool at http://localhost:8000/___graphql, you should see nodes for inbound and outbound references in each of your mdx files!
Turns into...
Since the Hello, World note contained a link to "Second Note," Second Note is aware of it and lists it in the inboundReferences
array. Pretty sweet!
We can use this to list linked references to each note file we have, a la Roam Research, Obsidian, and all the other fancy note apps.
Let's set it up in our noteTemplate.js
file.
First, let's add it to our GraphQL query:
//noteTemplate.js
export const query = graphql`
query($slug: String!) {
mdx(slug: { eq: $slug }) {
body
inboundReferences {
... on Mdx {
frontmatter {
title
}
slug
}
}
}
}
`
Then we can map a simple new array that lists the references in a <ul>
tag. I added a ternary operator for the "Referenced in:" line, so it wouldn't display if there were no references.
//inside noteTemplate.js
return (
<article>
<MDXRenderer>{mdx.body}</MDXRenderer>
{mdx.inboundReferences.length > 0 ? <p>Referenced in:</p> : ""}
<ul>
{mdx.inboundReferences.map(ref => (
<li>
<Link to={`/notes/${ref.slug}`}>{ref.frontmatter.title}</Link>
</li>
))}
</ul>
<Link to="/">Back Home</Link>
</article>
)
Not so hard, right? Our Second Note page should now look like this:
The Cherry on Top: MDX Shortcodes
None of the early MDX config would be worth it if we didn't try out some custom components. The Gatsby MDX Docs are pretty thorough, so I'll be brief. I'm using the lovely MDX Embed for Gatsby, a freshly updated plugin worked on by some really nice people. It requires no imports.
Simply:
npm install mdx-embed gatsby-plugin-mdx-embed --save
then
// gatsby-config.js
module.exports = {
...
plugins: [
...
`gatsby-plugin-mdx-embed`
]
...
}
It's a great plugin with lots of different embeddable content. Try it out!
Obsidian autosaves, so it'll crash the dev server if you type in it for too long. But just for fun:
If you want to see what this system looks like with some styling (and a newly added Algolia search), I set it up on the Notes section of my personal website. Thanks for reading, and if you make something cool with this or want to chat, feel free to send me an email or say hi on Twitter.
Top comments (10)
Hi Joseph,
Thanks for the great tutorial. Did you continue this exploration? In particular, any luck generating a graph from the links?
I have created a Gatsby starter that will create a static site using your obsidian - and it has Graphs feature built into it. You can find the tool at github.com/binnyva/gatsby-garden
To see a sample site built using it, check my personal Digital Garden at notes.binnyva.com
Nice! I will give it a try. The site looks nice on my iPhone but the hamburger menu didn’t seem to work. I will try on my laptop later. Thanks for letting me know!
Hey Nascif! I did end up creating a graph for my notes, but I did it through redesigning my personal site using Next.js and Sanity. I'm just starting to work on a blog post about the redesign now. I'm using the react-force-graph package found here: npmjs.com/package/react-force-graph You can check out the graph by visiting any individual note page at seph.news/notes. Each graph is unique to the page you're own, which took a little while to figure out, but I'm happy with it.
Hi Joe, Great article 👏 I'm wondering how did you create the markdown references in nextjs did you reimplement the logic or used
gatsby-transformer-markdown-references
?Hey Dennis! Nah, I did something totally different-- I wrote a simple GROQ query for them using Sanity.io. Finding backlinks for a given document takes one line of code, it's pretty sweet. I'm working on a new blog post for this system today!
Any ideas on how to handle aliases in the backlinks/wikilinks? For example, if you used
[[Second Note | myAlias]]
the link will evaluate to/second-note-or-my-alias
AND on your website it'll render asSecond Note | my Alias
.So far my workaround is to just use relative page links via markdown links instead of wikilinks, making sure the filenames are named exactly the same as the relative link (medium.com/@sgpropguide/relative-p...)
Here's an example:
dev-to-uploads.s3.amazonaws.com/up...
Hmm, I don't know! There is an Obsidian plugin that auto-converts your wikilinks to markdown link formatting, so maybe that would help you?
Just a heads up that the Gatsby API has shifted a bit.
Need to use
children { ... on Mdx { } }
rather than
childMdx
I started my Digital Garden in 2020 and have been tending to it all along.
Are any other people still doing this?