DEV Community

Cover image for Creating a DIY Digital Garden with Obsidian and Gatsby
Joe Holmes
Joe Holmes

Posted on • Updated on

Creating a DIY Digital Garden with Obsidian and Gatsby

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`
Enter fullscreen mode Exit fullscreen mode

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/`,
      },
    },
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
      },
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

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}}
---
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
          }
        }
      }
    }
  }
`

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
            },
          },
        ]
      },
    },
]
Enter fullscreen mode Exit fullscreen mode

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)}`
Enter fullscreen mode Exit fullscreen mode

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`,
...
Enter fullscreen mode Exit fullscreen mode

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)
      },
    },
]
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

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>
  )
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

then


// gatsby-config.js
module.exports = {
  ...
  plugins: [
  ...
  `gatsby-plugin-mdx-embed`
  ]
  ...
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (8)

Collapse
nascif profile image
Nascif A Abousalh Neto

Hi Joseph,
Thanks for the great tutorial. Did you continue this exploration? In particular, any luck generating a graph from the links?

Collapse
binnyva profile image
Binny V A

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

Collapse
nascif profile image
Nascif A Abousalh Neto

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!

Collapse
joeholmes profile image
Joe Holmes Author

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.

Collapse
dennisseidel profile image
Dennis

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 ?

Thread Thread
joeholmes profile image
Joe Holmes Author

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!

Collapse
neldeles profile image
neldeles

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 as Second 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...

Collapse
joeholmes profile image
Joe Holmes Author

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?