<?xml version="1.0" encoding="utf-8"?>
  <?xml-stylesheet href="/feed.xsl" type="text/xsl"?>
    <feed xmlns="http://www.w3.org/2005/Atom">
      <title>Andrew Stiefel | coding</title>
      <link href="https://andrewstiefel.com/feed/topics/coding.xml" rel="self"/>
      <link href="https://andrewstiefel.com/topics/coding/" rel="alternate"/>
      <id>https://andrewstiefel.com/topics/coding/</id>
      <subtitle>A feed of posts about coding by Andrew Stiefel.</subtitle>
      <updated>2026-05-25T11:11:05-07:00</updated>
      <author>
        <name>Andrew Stiefel</name>
        <email></email>
      </author>
      <rights type="text">Copyright © 2026 {"name" => "Andrew Stiefel", "url" => "https://andrewstiefel.com", "linkedin" => "andrewstiefel", "codeberg" => "andrewstiefel"}. All rights reserved.</rights>
      <entry>
        <title>Building a personal monorepo for writing</title>
        <link rel="alternate" href="https://andrewstiefel.com/monorepo/"/>
        <published>2025-07-19T00:00:00-07:00</published>
        <id>https://andrewstiefel.com/personal-monorepo</id>
        <summary>How I created a workflow for researching and blogging with Obsidian and Jekyll</summary>
        <content type="html">&lt;p&gt;I use &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; to organize my thinking and writing. But I’ve always been dissatisfied with the process of turning what I write into a formatted post for my website. I either had to manage multiple vaults—adding complexity and violating one of my core principles of note-taking—or copy, paste, and edit my writing in another tool like VS Code.&lt;/p&gt;
          &lt;p&gt;I also prefer to keep my content separate from the website’s design, and I wanted to do that without introducing a CMS or additional tooling. Static site generators like Jekyll are great at converting Markdown into websites, but they usually require you to store content alongside all the other website files.&lt;/p&gt;
          &lt;p&gt;After some experimenting, I landed on a better solution: building a personal monorepo for my writing and publishing workflow.&lt;/p&gt;
          &lt;h2 id=&quot;why-use-a-monorepo&quot;&gt;Why use a monorepo?&lt;/h2&gt;
          &lt;p&gt;A &lt;em&gt;monorepo&lt;/em&gt;—short for monolithic repository—combines code for multiple projects into a single repository. A common pattern is to store both frontend and backend code in one place.&lt;/p&gt;
          &lt;p&gt;That’s actually a good analogy for how I think about writing. The “backend” is my Obsidian vault, where I research and take notes. The “frontend” is the website where I publish finished posts.&lt;/p&gt;
          &lt;h2 id=&quot;basic-setup&quot;&gt;Basic Setup&lt;/h2&gt;
          &lt;p&gt;To start ,I moved my Obsidian vault and Jekyll website into a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;personal-monorepo&lt;/code&gt; directory structured like this:&lt;/p&gt;
          &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/personal-monorepo
          ├── /notes
          └── /website
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;If you don’t already have an Obsidian vault or Jekyll site, you can easily replicate this from scratch. Install Obsidian, open the &lt;strong&gt;/notes&lt;/strong&gt; directory using the “Open folder as vault” option, and you’re ready to go.&lt;/p&gt;
          &lt;p&gt;Setting up Jekyll requires a bit more technical work, but &lt;a href=&quot;https://jekyllrb.com/docs/installation/&quot;&gt;you can find installation instructions here&lt;/a&gt;.&lt;/p&gt;
          &lt;h2 id=&quot;configuring-obsidian&quot;&gt;Configuring Obsidian&lt;/h2&gt;
          &lt;p&gt;For this approach it’s important to make a directory where you will only store published posts. I’m going to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/notes/posts&lt;/code&gt; for this tutorial but you can configure this directory any way you wish.&lt;/p&gt;
          &lt;p&gt;I also recommend installing two Obsidian community plugins:&lt;/p&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;https://github.com/Vinzent03/obsidian-git&quot;&gt;Obsidian Git&lt;/a&gt; – adds version control and makes publishing easy&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://github.com/kepano/obsidian-permalink-opener&quot;&gt;Obsidian Permalink Opener&lt;/a&gt; – lets you open post URLs in your browser for previewing&lt;/li&gt;
          &lt;/ul&gt;
          &lt;p&gt;For Git, you can push commits manually or set up periodic syncs. For the Permalink Opener plugin, I added both my website’s base URL and my local development URL so I can preview posts in the browser while editing.&lt;/p&gt;
          &lt;p&gt;With that we’re ready to move on to building our website.&lt;/p&gt;
          &lt;h2 id=&quot;connecting-jekyll-and-obsidian&quot;&gt;Connecting Jekyll and Obsidian&lt;/h2&gt;
          &lt;p&gt;By default, Jekyll looks for posts in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/website/_posts&lt;/code&gt;. But I want it to use the content in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/notes/posts&lt;/code&gt;. The simplest solution is to create a symbolic link.&lt;/p&gt;
          &lt;p&gt;First, open Terminal and make sure you’re in the root of your monorepo (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/personal-monorepo&lt;/code&gt;). &lt;strong&gt;Back up your content&lt;/strong&gt;, then delete the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts&lt;/code&gt; directory:&lt;/p&gt;
          &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; website/_posts
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Next, create a symlink that points to your Obsidian posts:&lt;/p&gt;
          &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;website
          &lt;span class=&quot;nb&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; ../notes/_posts _posts
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Folder names in symlinks are case-sensitive. If your Obsidian vault uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Posts&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;posts&lt;/code&gt;, your symlink won’t work with the above command. Folder names must match exactly.&lt;/p&gt;
          &lt;h2 id=&quot;publishing-with-netlify&quot;&gt;Publishing with Netlify&lt;/h2&gt;
          &lt;p&gt;&lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; provides first-class support for working with monorepos, but you’ll need to do a little additional configuration.&lt;/p&gt;
          &lt;p&gt;In your project dashboard or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netlify.toml&lt;/code&gt; file, set the base directory to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;website&lt;/code&gt;, and make sure the build command installs dependencies before running Jekyll:&lt;/p&gt;
          &lt;p&gt;Here’s what my full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netlify.toml&lt;/code&gt; looks like, and what is reflected in my dashboard:&lt;/p&gt;
          &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;base = &quot;website&quot;&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;command = &quot;bundle install &amp;amp;&amp;amp; bundle exec jekyll build&quot;&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;publish = &quot;_site&quot;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Netlify follows symlinks automatically, but Jekyll needs one extra setting. In your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;website/_config.yml&lt;/code&gt;, add:&lt;/p&gt;
          &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Enable access to symlinked content&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;lax_symlink_lookup&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;This lets Jekyll access files stored outside its root directory (e.g. the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;website&lt;/code&gt; folder). Without the site will build but won’t pull in your content from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notes/_posts&lt;/code&gt; directory.&lt;/p&gt;
          &lt;h2 id=&quot;final-workflow&quot;&gt;Final Workflow&lt;/h2&gt;
          &lt;p&gt;I have Obsidian configured to add new notes to my writing inbox under the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~inbox&lt;/code&gt; directory. Anything I start writing goes there. Once I’ve finished writing a note, I can then either move it into my permanent notes or add it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;posts&lt;/code&gt; directory if I want to publish it. Once I push the commit Netlify will build and publish my site.&lt;/p&gt;
          &lt;p&gt;While I’m writing, I can preview what the note will look like by starting the Jekyll development server (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec jekyll serve&lt;/code&gt;) and adding the note to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;posts&lt;/code&gt; directory. I can use the Obsidian hotlinks command to open a preview of the post in my browser.&lt;/p&gt;
          &lt;p&gt;To wrap up, your final workflow looks something this this:&lt;/p&gt;
          &lt;ol&gt;
          &lt;li&gt;Write, edit, and link notes in Obsidian&lt;/li&gt;
          &lt;li&gt;Commit and push changes to Github&lt;/li&gt;
          &lt;li&gt;Netlify will pick up the changes and publish your posts&lt;/li&gt;
          &lt;/ol&gt;
          &lt;h2 id=&quot;bonus-convert-backlinks-to-weblinks&quot;&gt;Bonus: Convert Backlinks to Weblinks&lt;/h2&gt;
          &lt;p&gt;It’s outside the scope of this post, but I also wrote a custom plugin to convert backlinks to web links. That way I can use Obsidian’s backlinks within my published posts.&lt;/p&gt;
          &lt;p&gt;&lt;a href=&quot;https://github.com/andrewstiefel/andrewstiefel.com/blob/main/_plugins/backlinks.rb&quot;&gt;You can grab the code here&lt;/a&gt;.&lt;/p&gt;
          &lt;h2 id=&quot;additional-resources&quot;&gt;Additional Resources&lt;/h2&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;https://alexoliveira.cc/guide/jekyll-with-obsidian&quot;&gt;Jekyll Blogging with Obsidian&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://refinedmind.co/obsidian-jekyll-workflow&quot;&gt;Obsidian Jekyll workflow&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://dev.to/adrianogil/blogging-with-obsidian-and-jekyll-5bgl&quot;&gt;Blogging with Obsidian and Jekyll&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://stephango.com/vault&quot;&gt;How I Use Obsidian&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        </content>
      </entry>
      <entry>
        <title>How I use GitHub as a CMS</title>
        <link rel="alternate" href="https://andrewstiefel.com/github-cms-blog/"/>
        <published>2025-02-02T00:00:00-08:00</published>
        <id>https://andrewstiefel.com/github-cms-blog</id>
        <summary>GitHub makes it easy to build a free content management system for your blog.</summary>
        <content type="html">&lt;p&gt;I use &lt;a href=&quot;/blog-jekyll-netlify/&quot; class=&quot;internal-link&quot; data-preview-title=&quot;How I built my blog with Jekyll and Netlify&quot; data-preview-excerpt=&quot;I’ve been blogging and hosting my website since 2006, but I’ve always been unhappy with the themes available for technologies like Blogger, WordPress, or Squarespace. I usually had a vision for what I wanted to create and would spend hours scouring marketplaces to find something that came close.&quot;&gt;Jekyll to build and publish my blog on Netlify&lt;/a&gt;. For a long time my content management system (CMS) was just a bunch of markdown files on my laptop. Most of the time I don’t need anything else — it’s simple, and it’s just me.&lt;/p&gt;
          &lt;p&gt;But as I’ve wanted to focus on writing more often, I wanted to find an easier way to track future ideas, what I’d like to work on now, and easily see what I’ve done.&lt;/p&gt;
          &lt;p&gt;That’s where GitHub comes in.&lt;/p&gt;
          &lt;h2 id=&quot;why&quot;&gt;Why?&lt;/h2&gt;
          &lt;p&gt;GitHub is built for software development, but many of the features work just as well for content development. In fact, is has about everything I need as you’ll see in my setup section below. This includes templates, project boards, and workflows for publishing.&lt;/p&gt;
          &lt;p&gt;Plus it’s easy to review and collaborate with other people. I don’t do that much as a solo writer, but I do get readers who spot typos or who share feedback—and then it’s easy to track and add their contributions.&lt;/p&gt;
          &lt;p&gt;But let’s be honest — this is one of the projects I took on because I could, not so much because it really saves me any time. Choose your adventure accordingly :)&lt;/p&gt;
          &lt;h2 id=&quot;basic-setup&quot;&gt;Basic setup&lt;/h2&gt;
          &lt;p&gt;If you’re already using GitHub to host the code for your blog (like I do with Jekyll), then you can get started with this in 15 minutes or less!&lt;/p&gt;
          &lt;ol&gt;
          &lt;li&gt;&lt;strong&gt;Create a project:&lt;/strong&gt; Go to the GitHub repository for your blog (or make a new one!) and select projects and &lt;a href=&quot;https://docs.github.com/en/issues/planning-and-tracking-with-projects/creating-projects/creating-a-project&quot;&gt;create your project&lt;/a&gt;.&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Choose a format:&lt;/strong&gt; GitHub projects lets you &lt;a href=&quot;https://docs.github.com/en/issues/planning-and-tracking-with-projects/customizing-views-in-your-project&quot;&gt;customize views&lt;/a&gt; of items in your project using a Kanban board or table format. I recommend starting with the Kanban format. You can always change this later.&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Organize your workflow:&lt;/strong&gt; Once you’ve created your project, take a couple minutes to configure your workflow. I have sections for no status (my backlog), in progress, ready, and done.&lt;/li&gt;
          &lt;/ol&gt;
          &lt;p&gt;You can &lt;a href=&quot;https://github.com/users/andrewstiefel/projects/2&quot;&gt;see an example of mine on GitHub&lt;/a&gt;, or with the screenshot below:&lt;/p&gt;
          &lt;p&gt;&lt;img src=&quot;https://andrewstiefel.com/assets/img/github-cms.png&quot; data-lightbox=&quot;&quot; data-full=&quot;https://res.cloudinary.com/andrewstiefel/image/fetch/q_auto,f_auto/https://andrewstiefel.com/assets/img/github-cms.png&quot; alt=&quot;GitHub CMS&quot; width=&quot;2990&quot; height=&quot;1712&quot; crossorigin=&quot;anonymous&quot; class=&quot;dark:brightness-75 cursor-pointer&quot; /&gt;&lt;/p&gt;
          &lt;h2 id=&quot;use-issues-to-track-posts&quot;&gt;Use issues to track posts&lt;/h2&gt;
          &lt;p&gt;Once you’ve created the basic structure, it’s time to start tracking your writing! You’ll want to create new issues so you can track your progress. I decided to create a new template so I wouldn’t have to add the same info every time.&lt;/p&gt;
          &lt;p&gt;If you’d like to create one, all you need to add is a markdown file in your repository at the location below:&lt;/p&gt;
          &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.
          ├── .github 
          │   └── ISSUE_TEMPLATE
          │      └── cms.md
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Here’s &lt;a href=&quot;https://github.com/andrewstiefel/andrewstiefel.com/blob/main/.github/ISSUE_TEMPLATE/cms.md?plain=1&quot;&gt;an example&lt;/a&gt; of what mine looks like:&lt;/p&gt;
          &lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cms&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;about&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Submit an idea for the blog&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[blog&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;post]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cms&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;assignees&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;
          &lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
          &lt;span class=&quot;gs&quot;&gt;**What is this post about?**&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;gs&quot;&gt;**Outline**&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;1.&lt;/span&gt; 
          &lt;span class=&quot;gs&quot;&gt;**Tasks**&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Write outline
          &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Draft blog post
          &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Find (1) reviewer
          &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Create pull request
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;I use the format &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[blog post] example title&lt;/code&gt; so I can see at a glance that the issue covers a new blog post, and roughly what it’s about. I provide a brief description (usually when I first have the idea). Later I’ll come back and outline the post.&lt;/p&gt;
          &lt;h2 id=&quot;writing-and-publishing-posts&quot;&gt;Writing and publishing posts&lt;/h2&gt;
          &lt;p&gt;Jekyll comes with basic structure which makes it easy to get started. I save my draft writing in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_drafts&lt;/code&gt; folder and move finished posts into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts&lt;/code&gt; folder.&lt;/p&gt;
          &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.
          ├── _drafts 
          ├── _posts
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;But you have a few options to customize your workflow a bit further:&lt;/p&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;strong&gt;Work directly in your main branch&lt;/strong&gt; and publish by moving the your finished drafts from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_drafts&lt;/code&gt; folder to the  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts&lt;/code&gt; folder.&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Create a new branch&lt;/strong&gt; for each post as you work and merge into your main branch when you’re ready to publish.&lt;/li&gt;
          &lt;/ul&gt;
          &lt;blockquote&gt;
          &lt;p&gt;&lt;strong&gt;JEKYLL TIP&lt;/strong&gt; 
          You can preview your posts in your development environment as you work! Just append the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--&lt;/code&gt;drafts flag to the build or serve command. For example,  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll serve --drafts&lt;/code&gt;. Each draft post will be added using the last modified time as the publication date.&lt;/p&gt;
          &lt;/blockquote&gt;
          &lt;p&gt;I prefer to create a new branch and to submit a pull request when I’m ready to publish (although I’ll admit, I’m not super consistent since it’s just me). Either way, when you’re ready to publish, you’ll &lt;a href=&quot;https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue&quot;&gt;close the issue&lt;/a&gt; for your blog by writing a commit message that includes the number of the issue. For example, when I published this blog, I used “Closes 212” to tell GitHub to mark the issue as done.&lt;/p&gt;
          &lt;p&gt;Now if you visit your project, you’ll see your post has automatically been moved to the “done” column!&lt;/p&gt;
          &lt;p&gt;&lt;img src=&quot;https://andrewstiefel.com/assets/img/github-pull-request.png&quot; data-lightbox=&quot;&quot; data-full=&quot;https://res.cloudinary.com/andrewstiefel/image/fetch/q_auto,f_auto/https://andrewstiefel.com/assets/img/github-pull-request.png&quot; alt=&quot;GitHub Pull Request&quot; width=&quot;1166&quot; height=&quot;474&quot; crossorigin=&quot;anonymous&quot; class=&quot;dark:brightness-75 cursor-pointer&quot; /&gt;&lt;/p&gt;
          &lt;h2 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h2&gt;
          &lt;p&gt;I’ve been exploring ways to &lt;a href=&quot;https://docs.github.com/en/issues/planning-and-tracking-with-projects/customizing-views-in-your-project&quot;&gt;customize this further by creating new views&lt;/a&gt; — for example, a table view that adds publication dates so I can when I want to publish a post. But most importantly, I’m enjoying the flexibility and close integration between what I write and how it’s published.&lt;/p&gt;
          &lt;h2 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h2&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;/blog-jekyll-netlify/&quot; class=&quot;internal-link&quot; data-preview-title=&quot;How I built my blog with Jekyll and Netlify&quot; data-preview-excerpt=&quot;I’ve been blogging and hosting my website since 2006, but I’ve always been unhappy with the themes available for technologies like Blogger, WordPress, or Squarespace. I usually had a vision for what I wanted to create and would spend hours scouring marketplaces to find something that came close.&quot;&gt;How I Built My Blog with Jekyll and Netlify&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;/markdown-files-not-apps/&quot; class=&quot;internal-link&quot; data-preview-title=&quot;Markdown files, not apps&quot; data-preview-excerpt=&quot;Files, not apps. I&amp;#39;m convinced this is the best way to work in the future.\n\nI write down almost everything important in my life: lists, ideas, plans, code, and articles. They form my extended memory. They are a record of what I&amp;#39;ve done, and who I&amp;#39;ve been.&quot;&gt;Markdown Files, Not Apps&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        </content>
      </entry>
      <entry>
        <title>Working with multiple GitHub accounts and SSH keys</title>
        <link rel="alternate" href="https://andrewstiefel.com/working-multiple-github-accounts/"/>
        <published>2024-05-09T00:00:00-07:00</published>
        <id>https://andrewstiefel.com/working-multiple-github-accounts</id>
        <summary>Learn how to use 1Password to sign Git commits for multiple GitHub accounts on one machine</summary>
        <content type="html">&lt;p&gt;I have multiple GitHub accounts – one for work, one for demos, and one for personal projects. Each has a unique email address, password, and 2FA associated with it. They also each have a unique SSH key. In fact, the SSH keys are all saved in different 1Password accounts (personal, demo, and work).&lt;/p&gt;
          &lt;p&gt;In some cases I perform the development on the same device, like when I’m building a demo on my work device. In that case I need to make sure that I’m using the correct GitHub account and SSH key.&lt;/p&gt;
          &lt;p&gt;I use 1Password as my &lt;a href=&quot;https://developer.1password.com/docs/ssh&quot;&gt;SSH agent&lt;/a&gt; because it keeps the private keys off my device, plus it makes authorizing SSH connections a breeze. I can authenticate a connection using Touch ID, so just a fingerprint touch on my keyboard and then I’m off to my next task.&lt;/p&gt;
          &lt;p&gt;Fortunately, this only takes a few minutes to setup!&lt;/p&gt;
          &lt;h2 id=&quot;organize-your-local-directory&quot;&gt;Organize your local directory&lt;/h2&gt;
          &lt;p&gt;All my repositories are saved under a GitHub folder and then I use directories to organize my projects by account. Although I prefer to keep personal and work separate, you could configure all three accounts for a single device like in the example below:&lt;/p&gt;
          &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/github
          ├── /demo
          ├── /personal
          └── /work
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;This will make it very easy to configure which profile and SSH key to use in the following steps.&lt;/p&gt;
          &lt;h2 id=&quot;set-your-global-gitconfig-file&quot;&gt;Set your global gitconfig file&lt;/h2&gt;
          &lt;p&gt;In this example, I’m setting my personal Git configuration as the global default. Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[includeIf]&lt;/code&gt; I specify a different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitconfig&lt;/code&gt; file for the &lt;strong&gt;demo &lt;/strong&gt;and &lt;strong&gt;work &lt;/strong&gt;directories.&lt;/p&gt;
          &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;user]
          name &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &amp;lt;github_personal_name&amp;gt;
          email &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &amp;lt;github_personal_email&amp;gt;
          signingkey &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &amp;lt;your_ssh_key&amp;gt;
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;gpg]
          format &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ssh
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;gpg &lt;span class=&quot;s2&quot;&gt;&quot;ssh&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
          program &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/Applications/1Password.app/Contents/MacOS/op-ssh-sign&quot;&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;commit]
          gpgsign &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;includeIf &lt;span class=&quot;s2&quot;&gt;&quot;gitdir:~/github/demo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
          path &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ~/github/demo/.gitconfig
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;includeIf &lt;span class=&quot;s2&quot;&gt;&quot;gitdir:~/github/work&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
          path &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ~/github/work/.gitconfig
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h2 id=&quot;create-gitconfig-files-for-each-directory&quot;&gt;Create gitconfig files for each directory&lt;/h2&gt;
          &lt;p&gt;Next I’ll create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitconfig&lt;/code&gt; files and save them in the &lt;strong&gt;demo &lt;/strong&gt;and &lt;strong&gt;work &lt;/strong&gt;directories. I’ll use a similar template, but this time I’ll provide the GitHub name, email, and public signing key for my demo and work accounts.&lt;/p&gt;
          &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;user]
          name &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &amp;lt;github_work_name&amp;gt;
          email &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &amp;lt;github_work_email&amp;gt;
          signingkey &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &amp;lt;your_ssh_key&amp;gt;
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;gpg]
          format &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ssh
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;gpg &lt;span class=&quot;s2&quot;&gt;&quot;ssh&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
          program &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/Applications/1Password.app/Contents/MacOS/op-ssh-sign&quot;&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;commit]
          gpgsign &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h2 id=&quot;configure-the-ssh-agent&quot;&gt;Configure the SSH agent&lt;/h2&gt;
          &lt;p&gt;In order to call the correct SSH keys, I’ll need to update the SSH agent config file located at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/config&lt;/code&gt;.&lt;/p&gt;
          &lt;p&gt;I start by setting the 1Password SSH agent as the default for all hosts. Then I create custom hosts for each account, in this case&lt;strong&gt; Demo&lt;/strong&gt;,&lt;strong&gt; Personal&lt;/strong&gt;, and&lt;strong&gt; Work&lt;/strong&gt;.&lt;/p&gt;
          &lt;p&gt;Next, I downloaded the public keys from 1Password for my demo, personal, and work accounts. I renamed each file (for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;demo_git.pub&lt;/code&gt;) and saved the all to my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/&lt;/code&gt; folder.&lt;/p&gt;
          &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# By default, use the 1Password SSH agent for all hosts&lt;/span&gt;
          Host &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
          IdentityAgent &lt;span class=&quot;s2&quot;&gt;&quot;~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock&quot;&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;# Demo GitHub&lt;/span&gt;
          Host gh-demo
          HostName github.com
          User git
          IdentityFile ~/.ssh/demo_git.pub
          IdentitiesOnly &lt;span class=&quot;nb&quot;&gt;yes&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;# Personal GitHub&lt;/span&gt;
          Host gh-personal
          HostName github.com
          User git
          IdentityFile ~/.ssh/personal_git.pub
          IdentitiesOnly &lt;span class=&quot;nb&quot;&gt;yes&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;# Work GitHub&lt;/span&gt;
          Host gh-work
          HostName github.com
          User git
          IdentityFile ~/.ssh/work_git.pub
          IdentitiesOnly &lt;span class=&quot;nb&quot;&gt;yes&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h2 id=&quot;reset-your-local-repositories&quot;&gt;Reset your local repositories&lt;/h2&gt;
          &lt;p&gt;Finally, I’ll need to reset each individual repository so it uses one of the hosts specified above. This will make sure it uses the correct SSH key to authenticate with GitHub and when pushing Git commits.&lt;/p&gt;
          &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git remote set-url origin &amp;lt;host&amp;gt;:&amp;lt;organization&amp;gt;/&amp;lt;repository&amp;gt;.git
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Finally, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git remote -v&lt;/code&gt; to confirm that Git is using the correct remote repository. You can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git fetch&lt;/code&gt; to confirm that 1Password offers up the correct SSH key for authentication.&lt;/p&gt;
          &lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;https://developer.1password.com/docs/ssh/git-commit-signing#configure-multiple-commit-signing-setups&quot; title=&quot;Configure Multiple Git Commit Signing Setups&quot;&gt;Configure Multiple Git Signing Setups&lt;/a&gt; (1Password)&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://developer.1password.com/docs/ssh/agent/advanced#use-multiple-github-accounts&quot; title=&quot;Use Multiple GitHub Accounts&quot;&gt;Use Multiple GitHub Accounts&lt;/a&gt; (1Password)&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account&quot; title=&quot;Adding a New SSH Key to Your GitHub Account&quot;&gt;Adding a New SSH Key to Your GitHub Account&lt;/a&gt; (GitHub)&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://developer.1password.com/docs/ssh/git-commit-signing#step-2-register-your-public-key&quot; title=&quot;Register a Public SSH Key with GitHub&quot;&gt;Register a Public SSH Key with GitHub&lt;/a&gt; (1Password)&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/testing-your-ssh-connection&quot; title=&quot;Testing Your SSH Connection&quot;&gt;Testing Your SSH Connection&lt;/a&gt; (GitHub)&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey#verify-the-public-key-is-attached-to-your-account&quot; title=&quot;Switching Remote URLs from HTTPS to SSH&quot;&gt;Switching Remote URLs from HTTPS to SSH&lt;/a&gt; (GitHub)&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://dev.to/sisco/optimize-your-git-setup-strategies-for-handling-multiple-github-accounts-3ji8&quot; title=&quot;Optimize Your Git Setup&quot;&gt;Optimize Your Git Setup: Strategies for Handling Multiple GitHub Accounts&lt;/a&gt; (Dev.to)&lt;/li&gt;
          &lt;/ul&gt;
        </content>
      </entry>
      <entry>
        <title>Build an email subscription form with Netlify Functions</title>
        <link rel="alternate" href="https://andrewstiefel.com/netlify-functions-email-subscription/"/>
        <published>2022-08-28T00:00:00-07:00</published>
        <id>https://andrewstiefel.com/netlify-functions-email-subscription</id>
        <summary>Learn how to use Netlify Functions and ConvertKit to create a custom newsletter subscription form for your website.</summary>
        <content type="html">&lt;p&gt;I enjoy the process of building and maintaining my own personal website. It’s a great way to experiment with different technologies, and have fun learning new tools and concepts along the way.&lt;/p&gt;
          &lt;p&gt;This time, I wanted to learn how to use &lt;a href=&quot;https://www.netlify.com/blog/intro-to-serverless-functions/&quot; title=&quot;Intro to Serverless Functions – Netlify&quot;&gt;serverless functions&lt;/a&gt;. There are a lot of great resources out there already. But I had trouble finding a guide that adequately addressed my use case: a humble email subscription form.&lt;/p&gt;
          &lt;p&gt;This tutorial is strongly inspired by an &lt;a href=&quot;https://css-tricks.com/using-netlify-forms-and-netlify-functions-to-build-an-email-sign-up-widget/&quot; title=&quot;CSS-Tricks&quot;&gt;excellent guide created by Matthew Ström&lt;/a&gt;. I’ve added solutions for some of the problems I encountered while following his guide, but Matthew deserves the credit.&lt;/p&gt;
          &lt;h2 id=&quot;the-challenge-build-a-mailing-list-sign-up-form&quot;&gt;The Challenge: Build a Mailing List Sign Up Form&lt;/h2&gt;
          &lt;p&gt;After going through all the work to build my personal website, the last thing I wanted to do was slap a pre-designed newsletter subscription form on it.&lt;/p&gt;
          &lt;p&gt;I wanted the flexibility to design my own custom forms for a few reasons:&lt;/p&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;strong&gt;Better design:&lt;/strong&gt; Email marketing providers offer great default forms, but they never perfectly match a site’s design&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Better Performance:&lt;/strong&gt; External email forms require additional calls for CSS and Javascript, and they sometimes get blocked by privacy settings&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Better privacy:&lt;/strong&gt; Hosted email forms can collect additional information about users, like their IP address&lt;/li&gt;
          &lt;/ul&gt;
          &lt;p&gt;I set out a few rules for this challenge:&lt;/p&gt;
          &lt;ul&gt;
          &lt;li&gt;It should work without extra JavaScript or AJAX&lt;/li&gt;
          &lt;li&gt;It must use &lt;a href=&quot;https://docs.netlify.com/functions/overview/&quot; title=&quot;Netlify Functions Overview&quot;&gt;Netlify functions&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;It shouldn’t need external dependencies&lt;/li&gt;
          &lt;/ul&gt;
          &lt;h2 id=&quot;the-team-jekyll-netlify-and-convertkit&quot;&gt;The Team: Jekyll, Netlify, and ConvertKit&lt;/h2&gt;
          &lt;p&gt;My website is built using a static site generator called &lt;a href=&quot;https://jekyllrb.com/&quot; title=&quot;Jekyll&quot;&gt;Jekyll&lt;/a&gt;. It allows me to build my own templates and components, so I’ll use it to build my email form. I also used &lt;a href=&quot;https://github.com/tailwindlabs/tailwindcss-forms&quot; title=&quot;Github – Tailwind CSS Forms&quot;&gt;Tailwind CSS Forms Plugin&lt;/a&gt; to simplify the design process.&lt;/p&gt;
          &lt;p&gt;I use a service called &lt;a href=&quot;https://www.netlify.com/&quot; title=&quot;Netlify&quot;&gt;Netlify&lt;/a&gt; to deploy my website. It’s key for this project, because it complies the static assets built by Jekyll and runs the serverless function to send emails to my email list provider.&lt;/p&gt;
          &lt;p&gt;Finally, I’m using &lt;a href=&quot;https://convertkit.com/&quot; title=&quot;ConvertKit&quot;&gt;ConvertKit&lt;/a&gt; as my email list provider. I’ve also included information on how to use &lt;a href=&quot;https://buttondown.email&quot; title=&quot;Buttondown&quot;&gt;Buttondown&lt;/a&gt; and &lt;a href=&quot;https://getsendstack.com/&quot; title=&quot;SendStack&quot;&gt;SendStack&lt;/a&gt; in this tutorial, but the basic principles of the function should apply to any other email providers that offers an API.&lt;/p&gt;
          &lt;p&gt;Ok, let’s get started!&lt;/p&gt;
          &lt;h2 id=&quot;create-the-serverless-function&quot;&gt;Create the serverless function&lt;/h2&gt;
          &lt;p&gt;You need to follow three basic steps to create a Netlify function:&lt;/p&gt;
          &lt;ol&gt;
          &lt;li&gt;Add API tokens to Netlify as environment variables via the admin interface&lt;/li&gt;
          &lt;li&gt;Tells Netlify where to look for your functions using the netlify.toml file 3. Write the function in a Javascript file in your project&lt;/li&gt;
          &lt;li&gt;Write the function as a Javascript file in your project&lt;/li&gt;
          &lt;/ol&gt;
          &lt;p&gt;To start, let’s save the API token from our email service as an &lt;strong&gt;environment variable&lt;/strong&gt;. Environment variables are useful to hold information that I don’t want to make public, like this API key. You can add an environment variable using the Netlify admin interface under your build and deploy settings.&lt;/p&gt;
          &lt;p&gt;&lt;img src=&quot;https://andrewstiefel.com/assets/img/netlify-environment-variables.png&quot; data-lightbox=&quot;&quot; data-full=&quot;https://res.cloudinary.com/andrewstiefel/image/fetch/q_auto,f_auto/https://andrewstiefel.com/assets/img/netlify-environment-variables.png&quot; alt=&quot;Netlify environment variables&quot; width=&quot;1880&quot; height=&quot;782&quot; crossorigin=&quot;anonymous&quot; class=&quot;dark:brightness-75 cursor-pointer&quot; /&gt;&lt;/p&gt;
          &lt;p&gt;Next, specify where Netlify should look for your functions. Edit your netlify.toml to specify the functions directory. It might look something like this:&lt;/p&gt;
          &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;base = &quot;.&quot;&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;functions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;directory = &quot;netlify/functions/&quot;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Create a function file in the directory you specified above. If you used the default functions directory, you should save your function at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YOUR_BASE_DIRECTORY/netlify/functions&lt;/code&gt;.&lt;/p&gt;
          &lt;p&gt;Next, you’ll need to give your function a name. For example, to create a function with an endpoint name of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello-world&lt;/code&gt;, save the function in one of these locations:&lt;/p&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netlify/functions/hello-world.js&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netlify/functions/hello-world/hello-world.js&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netlify/functions/hello-world/index.js&lt;/code&gt;&lt;/li&gt;
          &lt;/ul&gt;
          &lt;p&gt;For this tutorial, I’m going to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;submission-created&lt;/code&gt; event trigger. Netlify will run my function every time a form is submitted. To do that, I’m going to name my function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;submission-created.js&lt;/code&gt;.&lt;/p&gt;
          &lt;p&gt;Now you’re ready to start writing the function. Start by importing the API key you created earlier as an environment variable:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EMAIL_TOKEN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;On line 2, I add a small library called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-fetch&lt;/code&gt;. This allows me to use Javascript’s Fetch API, which is how we’ll format an API POST request to send data to our email service.&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;node-fetch&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;NOTE: When I was writing this post, many of the tutorials available used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; method to import the Fetch API which resulted in errors when I tried to deploy the function. Make sure you use the method I described above. If you upgrade to node-fetch v3, you’ll also need to update either your netlify.toml file or package.json to use ESM.&lt;/p&gt;
          &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;functions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;node_bundler = &quot;esbuild&quot;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;You can find more information about &lt;a href=&quot;https://www.netlify.com/blog/how-to-make-a-fetch-request-using-node-fetch-v3/&quot; title=&quot;Netlify Blog&quot;&gt;how to make a fetch request using node-fetch v3&lt;/a&gt; in an excellent guide by Tatyana Novell on the Netlify blog.&lt;/p&gt;
          &lt;p&gt;Next create a synchronous function on line 4. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports.handler&lt;/code&gt; value is where Netlify expects to find the function, so I define it there. The basic syntax to create the function is provided below:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// your server-side functionality&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Next retrieve the email from the event value using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON.parse&lt;/code&gt;:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Then log the data in the console for debugging:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Received a submission: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;After retrieving the email address from the event value using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON.parse&lt;/code&gt;, I’m ready to send it off my email marketing providers. I’ll use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-fetch&lt;/code&gt; library I imported earlier to form the POST request.&lt;/p&gt;
          &lt;p&gt;I’ve outlined code examples for a few services below, but make sure you consult the API documentation from your email provider to make sure the API request is properly format.&lt;/p&gt;
          &lt;h3 id=&quot;convertkit-subscription-form&quot;&gt;ConvertKit Subscription Form&lt;/h3&gt;
          &lt;p&gt;Using ConvertKit as an example, send a POST request to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://api.convertkit.com/v3/forms/&amp;lt;YOUR_FORM_ID/subscribe&lt;/code&gt;.&lt;/p&gt;
          &lt;p&gt;To find the form ID, navigate to &lt;strong&gt;Grow &amp;gt; Landing Pages &amp;amp; Forms&lt;/strong&gt;, select the form you want to use, and copy the ID number from the url:&lt;/p&gt;
          &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://app.convertkit.com/forms/designers/&lt;span class=&quot;nt&quot;&gt;&amp;lt;YOUR_FORM_ID&amp;gt;&lt;/span&gt;/edit
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;The body of the POST request contains the email token and the email address from the form submission:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://api.convertkit.com/v3/forms/&amp;lt;YOUR_FORM_ID/subscribe&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; 
          &lt;span class=&quot;na&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EMAIL_TOKEN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h3 id=&quot;buttondown-subscription-form&quot;&gt;Buttondown Subscription Form&lt;/h3&gt;
          &lt;p&gt;&lt;a href=&quot;https://buttondown.email/refer/andrewstiefel&quot; data-fathom=&quot;Referral click&quot;&gt;Buttondown’s API&lt;/a&gt; sends the authorization in the headers rather than the body, so you’ll need to adapt the code slightly:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://api.buttondown.email/v1/subscribers&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`Token &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EMAIL_TOKEN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h3 id=&quot;sendstack-subscription-form&quot;&gt;SendStack Subscription Form&lt;/h3&gt;
          &lt;p&gt;SendStack is a new privacy-first email service. Unlike Buttondown and ConvertKit, they offer API access on their free plan. There is currently a waiting list, but if you have access, you can try it out using the code below.&lt;/p&gt;
          &lt;p&gt;Add the email token to the headers and the email address to the body of the POST request:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://getsendstack.com/api/subscribers&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`Bearer &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EMAIL_TOKEN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Then capture and log the response from the email service. We do this to diagnose any issues that happened. Netlify makes it easy to check your function’s logs, so use console.log often!&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Response:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;responseText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Finally, redirect the form to a confirmation page that tells subscribers to check their emails to confirm their subscription. Use a simple return to redirect the browser to the new page:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Location&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/confirmation/,
          },
          }
          &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;You can find the completed function below. In this case I used ConvertKit:&lt;/p&gt;
          &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EMAIL_TOKEN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;node-fetch&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Received a submission: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://api.convertkit.com/v3/forms/{YOUR_FORM-ID}/subscribe&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; 
          &lt;span class=&quot;na&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EMAIL_TOKEN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
          &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;response:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;responseText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Location&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/confirmation/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h2 id=&quot;create-the-email-subscription-form&quot;&gt;Create the email subscription form&lt;/h2&gt;
          &lt;p&gt;Now that you’ve built the function, let’s call it from the email subscription form. The HTML for the email subscription form is very minimal.&lt;/p&gt;
          &lt;p&gt;All you need to do is call the function using the form action:&lt;/p&gt;
          &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;newsletter&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;POST&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;action=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/.netlify/functions/subscribe-email&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Your Email Address&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Email&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Address&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Subscribe&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;Make sure you specific input name (“email”) and make sure it matches the information you parse from the event value using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON.parse&lt;/code&gt;.&lt;/p&gt;
          &lt;p&gt;If you used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;submission-created&lt;/code&gt; trigger for your function like I did, you’ll need to change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;form&amp;gt;&lt;/code&gt; field slightly by adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-netlify=&quot;true&quot;&lt;/code&gt; to tell Netlify to process this form:&lt;/p&gt;
          &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;newsletter&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;POST&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-netlify=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Your Email Address&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Email&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Address&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Subscribe&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h2 id=&quot;deploy-the-function&quot;&gt;Deploy the function&lt;/h2&gt;
          &lt;p&gt;Now that I’ve written my function, configured my netlify.toml file, and added my environment variables, everything is ready to go. Deploying is painless: just set up Netlify’s GitHub integration, and your function will be deployed when your project is pushed.&lt;/p&gt;
          &lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
          &lt;p&gt;It took less than 50 lines of code to create my own email subscription form including custom HTML and a serverless function to add new emails to my list. I wrote it all in HTML, CSS, and JavaScript, and everything is served from my domain. Plus, my website visitors get a nice experience whether they have JavaScript enabled or not, and it will still serve even if they have advanced privacy protection enabled.&lt;/p&gt;
        </content>
      </entry>
      <entry>
        <title>How to style an Atom feed with XSLT</title>
        <link rel="alternate" href="https://andrewstiefel.com/style-atom-xsl/"/>
        <published>2022-01-24T00:00:00-08:00</published>
        <id>https://andrewstiefel.com/style-atom-xsl</id>
        <summary>Create a seamless user experience for your Atom or RSS feed with XLST stylesheets.</summary>
        <content type="html">&lt;p&gt;Maybe it’s nostalgia for the early web, but I love web feeds as a tool for following and reading content. Feeds are privacy-first and put the reader in control: you can opt out any time, choose your tool for reading, and organize them in any way you want.&lt;/p&gt;
          &lt;p&gt;But the UX experience is terrible.&lt;/p&gt;
          &lt;p&gt;Web feeds are meant to be machine-readable, so most users follow a link to an RSS or Atom feed and end up looking at something like this:&lt;/p&gt;
          &lt;p&gt;&lt;img src=&quot;https://andrewstiefel.com/assets/img/raw-atom-rss.png&quot; data-lightbox=&quot;&quot; data-full=&quot;https://res.cloudinary.com/andrewstiefel/image/fetch/q_auto,f_auto/https://andrewstiefel.com/assets/img/raw-atom-rss.png&quot; alt=&quot;Raw RSS or Atom feed&quot; width=&quot;760&quot; height=&quot;483&quot; crossorigin=&quot;anonymous&quot; class=&quot;dark:brightness-75 cursor-pointer&quot; /&gt;&lt;/p&gt;
          &lt;p&gt;This doesn’t have to be the case. RSS and Atom feeds can be human-readable with a little extra work. &lt;a href=&quot;/feed.xml&quot; target=&quot;_blank&quot; data-fathom=&quot;RSS subscription&quot;&gt;Here’s an example from my website&lt;/a&gt;. It’s simple and clean and provides some essential instructions on how to get started:&lt;/p&gt;
          &lt;p&gt;&lt;img src=&quot;https://andrewstiefel.com/assets/img/human-readable-atom-feed.png&quot; data-lightbox=&quot;&quot; data-full=&quot;https://res.cloudinary.com/andrewstiefel/image/fetch/q_auto,f_auto/https://andrewstiefel.com/assets/img/human-readable-atom-feed.png&quot; alt=&quot;Human-readable Atom or RSS feed&quot; width=&quot;760&quot; height=&quot;483&quot; crossorigin=&quot;anonymous&quot; class=&quot;dark:brightness-75 cursor-pointer&quot; /&gt;&lt;/p&gt;
          &lt;p&gt;Let’s explore how to implement this with Atom and an XSLT stylesheet.&lt;/p&gt;
          &lt;h2 id=&quot;why-atom-and-not-rss&quot;&gt;Why Atom, and not RSS?&lt;/h2&gt;
          &lt;p&gt;Great question, and one I don’t intend to answer fully here given the vast amounts of writing on this topic already. In short, Atom is a better format.&lt;/p&gt;
          &lt;p&gt;Specifically, I wanted the following:&lt;/p&gt;
          &lt;ul&gt;
          &lt;li&gt;Support for a full content payload in the feed&lt;/li&gt;
          &lt;li&gt;Wide support across feed readers&lt;/li&gt;
          &lt;li&gt;Extensibility and future-proof format&lt;/li&gt;
          &lt;/ul&gt;
          &lt;h2 id=&quot;first-create-the-feed-in-jekyll&quot;&gt;First, create the feed in Jekyll&lt;/h2&gt;
          &lt;p&gt;&lt;em&gt;If you’re using a different tech stack to create your website, you can &lt;a href=&quot;#create-the-xsl-file-to-style-the-feed&quot;&gt;skip this section&lt;/a&gt; and jump ahead to the part about creating an XSLT stylesheet.&lt;/em&gt;&lt;/p&gt;
          &lt;p&gt;You can either use the excellent &lt;a href=&quot;https://github.com/jekyll/jekyll-feed&quot; title=&quot;Jekyll Feed&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Jekyll Feed plugin&lt;/a&gt;, or create your own template using liquid tags and save it in your project as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feed.xml&lt;/code&gt;.&lt;/p&gt;
          &lt;p&gt;I chose to create my own template, so I could incorporate some additional markup. You can see my version below:&lt;/p&gt;
          &lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
          ---
          &lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml-stylesheet href=&quot;/assets/css/feed.xsl&quot; type=&quot;text/xsl&quot;?&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;feed&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2005/Atom&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ site.title }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ site.url }}{{ site.baseurl }}/feed.xml&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;self&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ site.url }}{{ site.baseurl }}/&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;subtitle&amp;gt;&lt;/span&gt;{{ site.description }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/subtitle&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;updated&amp;gt;&lt;/span&gt;{{ site.time | date_to_xmlschema }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/updated&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;{{ site.url }}/&lt;span class=&quot;nt&quot;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;author&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;{{ site.author.name }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;email&amp;gt;&lt;/span&gt;{{ site.author.email }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/email&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;rights&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Copyright © {{ site.time | date: &quot;%Y&quot; }} {{ site.author }}. All rights reserved.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rights&amp;gt;&lt;/span&gt;
          {% for post in site.posts %}
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;entry&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ post.title }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ site.url }}{{ post.url }}&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;updated&amp;gt;&lt;/span&gt;{{ post.date | date_to_xmlschema }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/updated&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;{{ site.url }}{{ site.baseurl }}{{ post.id }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;{{ post.description }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;content&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;html&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;{{ post.content | xml_escape }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/content&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/entry&amp;gt;&lt;/span&gt;
          {% endfor %}
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/feed&amp;gt;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;p&gt;&lt;strong&gt;Note on YAML Front Matter block:&lt;/strong&gt; It’s important to leave the dashes at the top of the file. This is necessary because Jekyll will not process a page with Liquid unless there is a YAML block at the top of the file.&lt;/p&gt;
          &lt;p&gt;&lt;strong&gt;Enable auto-discovery:&lt;/strong&gt; Make sure you add the appropriate meta tag to support automated discovery of your feed. Place the following code somewhere in your template’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section to output the necessary metadata:&lt;/p&gt;
          &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application/atom+xml&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ site.url }}/feed.xml&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;title=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ site.title }}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h2 id=&quot;create-the-xsl-file-to-style-the-feed&quot;&gt;Create the XSL file to style the feed&lt;/h2&gt;
          &lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms759096%28v=vs.85%29&quot; title=&quot;What Is XSLT?&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;We can use XSLT to style our XML&lt;/a&gt;. This makes our feeds more human-readable while supporting bots, aggregators, and search engines.&lt;/p&gt;
          &lt;p&gt;You’ll notice some similarities to HTML and CSS in the example below, but with a few semantic changes and special attributes.&lt;/p&gt;
          &lt;p&gt;First, you can place your CSS in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag like normal. You can also use some standard HTML markup like the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;section&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
          &lt;p&gt;There are a few special elements you can use, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;xsl:apply-templates&amp;gt;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;xsl:value-of&amp;gt;&lt;/code&gt;. I won’t cover these in detail during this tutorial, but W3Schools maintains &lt;a href=&quot;https://www.w3schools.com/xml/xsl_elementref.asp&quot; title=&quot;XSLT Reference&quot;&gt;a great XSLT reference&lt;/a&gt; if you want to learn about all these special elements.&lt;/p&gt;
          &lt;p&gt;This Github Gist shows an &lt;a href=&quot;https://gist.github.com/andrewstiefel/57a0a400aa2deb6c9fe18c6da4e16e0f&quot; target=&quot;blank&quot; rel=&quot;noopener noreferrer&quot;&gt;example of the XLST stylesheet I created for my website&lt;/a&gt;. I wrote some basic CSS styles to format it, but you could even tap into your site’s primary CSS file to keep the styling consistent.&lt;/p&gt;
          &lt;p&gt;Save your file as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feed.xsl&lt;/code&gt; and add the tag below to your XML file. Make sure the href tag points to the correct location and file name for your website.&lt;/p&gt;
          &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?xml-stylesheet href=&quot;/feed.xsl&quot; type=&quot;text/xsl&quot;?&amp;gt;
          &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
          &lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
          &lt;p&gt;With a little extra care and attention, we can improve the experience of using RSS and Atom feeds across the web. Now when you visit my feed, you are greeted with an explanation of how to get started and a formatted recap of my latest posts.&lt;/p&gt;
          &lt;p&gt;I made my own XML template and XSLT stylesheet based on the examples above. You can &lt;a href=&quot;https://andrewstiefel.com/feed.xml&quot; title=&quot;Andrew Stiefel&apos;s Feed&quot; target=&quot;_blank&quot; data-fathom=&quot;RSS subscription&quot;&gt;see it in action here&lt;/a&gt; or &lt;a href=&quot;https://gist.github.com/andrewstiefel/57a0a400aa2deb6c9fe18c6da4e16e0f&quot; title=&quot;Github Gist&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;download a version&lt;/a&gt; to adapt for your website.&lt;/p&gt;
          &lt;p&gt;Thanks for reading!&lt;/p&gt;
          &lt;h2 id=&quot;additional-reading&quot;&gt;Additional Reading&lt;/h2&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;https://interconnected.org/home/2020/07/29/improving_rss&quot; title=&quot;Interconnected by Matt Webb&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;How would I improve RSS? Three ideas (Interconnected by Matt Webb)&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://lepture.com/en/2019/rss-style-with-xsl&quot; title=&quot;Just Lepture&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;How to style an RSS feed (Just Lepture)&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://natclark.com/tutorials/xslt-style-rss-feed/&quot; title=&quot;Nat Clark&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Styling an RSS Feed with XSLT (Nat Clark)&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://aboutfeeds.com/&quot; title=&quot;About Feeds&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;About Feeds&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        </content>
      </entry>
      <entry>
        <title>How I built my blog with Jekyll and Netlify</title>
        <link rel="alternate" href="https://andrewstiefel.com/blog-jekyll-netlify/"/>
        <published>2021-12-20T00:00:00-08:00</published>
        <id>https://andrewstiefel.com/blog-jekyll-netlify</id>
        <summary>I used Jekyll and Netlify to build a custom personal website and blog.</summary>
        <content type="html">&lt;p&gt;I’ve been blogging and hosting my website since 2006, but I’ve always been unhappy with the themes available for technologies like Blogger, WordPress, or Squarespace. I usually had a vision for what I wanted to create and would spend hours scouring marketplaces to find something that came close.&lt;/p&gt;
          &lt;p&gt;At first, I experimented with developing child themes for WordPress. If you’re not familiar with WordPress, I basically overwrote the CSS stylesheets and built my own page and post templates using PHP. I even got good enough that I had clients who would hire me to make changes to their WordPress themes and installations.&lt;/p&gt;
          &lt;p&gt;Still, all of the tweaks and changes felt cobbled together. They &lt;em&gt;were&lt;/em&gt; cobbled together. I was building Frankenstein sites with dozens of overrides, plugins, and hacks that could break at any moment.&lt;/p&gt;
          &lt;h2 id=&quot;searching-for-something-better&quot;&gt;Searching for something better&lt;/h2&gt;
          &lt;p&gt;I wanted to escape the bloat of platforms like WordPress, or the restricted design options available with a platform like Squarespace. &lt;strong&gt;I wanted to go back to static HTML and CSS and I wanted to code it myself.&lt;/strong&gt;&lt;/p&gt;
          &lt;p&gt;After reading online, I came across &lt;a href=&quot;https://jekyllrb.com/&quot; title=&quot;Jekyll&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Jekyll&lt;/a&gt;. It’s an open-source &lt;a href=&quot;https://www.cloudflare.com/learning/performance/static-site-generator/&quot; title=&quot;What is a static site generator?&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;static site generator&lt;/a&gt;. That means it complies basic text files into static HTML so you don’t have to code every page by hand.&lt;/p&gt;
          &lt;p&gt;I built a few starter projects with Jekyll and quickly fell in love with it. While there are a variety of open-source static site generators available now, I still enjoy the relative simplicity of Jekyll for getting started. The liquid templating language is easy to understand and your HTML/CSS/JS work cleanly together.&lt;/p&gt;
          &lt;p&gt;Jekyll may not be the sleekest option — there are good reasons to pick something like &lt;a href=&quot;https://www.gatsbyjs.com/&quot; title=&quot;Gatsby JS&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Gatsby&lt;/a&gt; or &lt;a href=&quot;https://nextjs.org/&quot; title=&quot;Next JS&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Next.js&lt;/a&gt; if you work with javascript and react — I still think it is the most approachable option for beginners (like me).&lt;/p&gt;
          &lt;h2 id=&quot;to-use-a-cms-or-not&quot;&gt;To use a CMS or not?&lt;/h2&gt;
          &lt;p&gt;I built a few starter websites with Jekyll. One of the things I missed at first was the integration with my writing tool of choice (&lt;a href=&quot;https://ulysses.app/&quot; title=&quot;Ulysses App&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Ulysses&lt;/a&gt;). I also wanted some of the benefits of a CMS, like scheduling posts. Ulysses integrates with WordPress and &lt;a href=&quot;https://ghost.org/&quot; title=&quot;Ghost&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Ghost&lt;/a&gt; so you can publish from mobile and desktop. I decided to give Ghost a try.&lt;/p&gt;
          &lt;p&gt;As a CMS, I did love Ghost. It’s simple and focused on the writing experience. They have even embraced the &lt;a href=&quot;https://jamstack.org/what-is-jamstack/&quot; title=&quot;Jamstack&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Jamstack&lt;/a&gt; (Javascript, APIs, and markup) movement driving static websites and supporting headless designs. I also liked the idea of keeping my content separate from the frontend framework.&lt;/p&gt;
          &lt;p&gt;I worked with Ghost for a while but kept getting frustrated with the server upkeep that it invariable entailed. At first, I paid for a monthly subscription, but the functionality at the basic level is limited unless you spend $30/mo. At that level, you can either create your own theme to host with them or use their API to support a frontend framework built with Jekyll, Gatsby, or another tool. The cost is about the same as a good WordPress hosting provider, but that was more than I was willing to spend for the convenience of scheduling posts within a CMS.&lt;/p&gt;
          &lt;p&gt;I should note that Ghost also offers an option to self-host (&lt;a href=&quot;https://marketplace.digitalocean.com/apps/ghost&quot; title=&quot;Ghost App on Digital Ocean&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;starting at $5/mo through Digital Ocean&lt;/a&gt;) but I didn’t want to maintain the server myself. I already have enough reasons to not write without throwing in server updates…which were one of the reasons I wanted to leave WordPress.&lt;/p&gt;
          &lt;p&gt;Ultimately, I decided to forgo the CMS and focus on Jekyll. I’ll write more about this in the future, but Jekyll fits into my growing philosophy around working with markdown for personal knowledge management and publishing.&lt;/p&gt;
          &lt;h2 id=&quot;publishing-with-netlify&quot;&gt;Publishing with Netlify&lt;/h2&gt;
          &lt;p&gt;My first Jekyll website was published using Github pages. While I loved the option to publish a free project website directly from my repo, it was definitely more difficult to manage and came with a host of limitations.&lt;/p&gt;
          &lt;p&gt;Ultimately, I went with Netlify. They are a fantastic organization that makes it easy to build, deploy, and scale web projects. Using Jekyll, I can generate a website from my repo on Github and Netlify deploys it to edge servers across its network. Since I’m only serving static HTML files, this makes my websites incredibly fast for readers. And there is no server (on my end) to worry about securing, protecting, etc. Netlify handles the build and deploys the static files acrss the edge.&lt;/p&gt;
          &lt;h2 id=&quot;getting-set-up&quot;&gt;Getting set up&lt;/h2&gt;
          &lt;p&gt;I won’t go into too much detail on the development process here, but I will link to the resources that I found the most helpful for getting started:&lt;/p&gt;
          &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;https://getpoole.com/&quot; title=&quot;Poole&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Poole: &lt;/a&gt;Clean and concise foundational setup for Jekyll&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://www.netlify.com/blog/2020/04/02/a-step-by-step-guide-jekyll-4.0-on-netlify/&quot; title=&quot;Netlify&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;How to deploy Jekyll with Netlify&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/creating-a-github-pages-site-with-jekyll&quot; title=&quot;Github&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;How to deploy Jekyll with GitHub pages&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://formspree.io/&quot; title=&quot;Formspree&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Formspree:&lt;/a&gt; Open-source form solution for static websites&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://shopify.github.io/liquid/&quot; title=&quot;Shopify on GitHub&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Liquid template language reference&lt;/a&gt; from Shopify&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;https://docs.netlify.com/&quot; title=&quot;Netlify Docs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Getting started with Netlify&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
          &lt;p&gt;As one of the original static website generators, Jekyll benefits from a large community of open-source contributors and users who have documented their techniques and solutions. If you get stuck, there is most likely a tutorial with the answers you need!&lt;/p&gt;
          &lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;
          &lt;p&gt;Overall I’m pretty happy with the results of the project. I learned a lot along the way and ended up with a website that doesn’t look too bad and is unique to me. While I don’t need to make any changes to start writing, there are a few areas I plan to explore in the coming months:&lt;/p&gt;
          &lt;p&gt;&lt;strong&gt;Implement Tailwind CSS to replace Bulma&lt;/strong&gt;
          I used the &lt;a href=&quot;https://bulma.io/&quot; title=&quot;Bulma Docs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Bulma CSS Framework&lt;/a&gt; to scaffold the development of this project. Previously, I used Bootstrap for a few starter projects. Bulma is great, but I’ve been wanting something easier to customize. Enter &lt;a href=&quot;https://tailwindcss.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Tailwind CSS&lt;/a&gt;, which is a just-in-time, atomic framework for creating CSS. I can build and design the website, all within HTML. Then, I can filter out any unused classes, resulting in a very small file.&lt;/p&gt;
          &lt;blockquote&gt;
          &lt;p&gt;&lt;strong&gt;Update 2024-12-01:&lt;/strong&gt; I completed this work back in May 2022. I am currently using the excellent &lt;a href=&quot;https://github.com/vormwald/jekyll-tailwindcss&quot;&gt;jekyll-tailwindcss plugin&lt;/a&gt; to implement Tailwind CSS for my site. I wraps the Tailwind CSS CLI and lets you specify which version of Tailwind CSS you want to use.&lt;/p&gt;
          &lt;/blockquote&gt;
          &lt;p&gt;&lt;strong&gt;Buildout functionality for my digital garden&lt;/strong&gt;
          I expect to write about this more in the coming months, but I’m planning to treat this blog more like &lt;a href=&quot;https://maggieappleton.com/garden-history&quot; title=&quot;A Brief History of the Digital Garden&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;a digital garden&lt;/a&gt;. There are some core functionalities I want to add, like sides notes, bi-directional links, and search, to make the reading experience more rich and interconnected.&lt;/p&gt;
          &lt;blockquote&gt;
          &lt;p&gt;&lt;strong&gt;Update 2024-12-01:&lt;/strong&gt; I’m still working on this. Overall this feels a bit more like a distraction from writing than a priority. I have played around with introducing categories for evergreen, seedling, and budding notes to indicate how complete an idea/post is.&lt;/p&gt;
          &lt;/blockquote&gt;
          &lt;p&gt;&lt;strong&gt;Rebuild the site with Gatsby, Next, or another framework&lt;/strong&gt;
          I really like Jekyll, so this one isn’t urgent. But I’m starting to see how javascript frameworks like Gatsby, Next, or Vue could enable some new capabilities. One function that stands out to me is the ability to import markdown (my blog posts) from a separate Github repository. This way my thinking and writing could truly exist separately (and under version control!) from the frontend visual design.&lt;/p&gt;
          &lt;blockquote&gt;
          &lt;p&gt;&lt;strong&gt;Update 2024-12-01:&lt;/strong&gt; I no longer plan to do this for a variety of reasons. I may write more about why in the future, but the short version for now is that I &lt;em&gt;hate&lt;/em&gt; dependencies and heavy frameworks. I like how simple Jekyll is to run, and that the final result is really just a bunch of html files, one stylesheet, and one bit of non-critical, vanilla Javascript.&lt;/p&gt;
          &lt;/blockquote&gt;
          &lt;p&gt;I hope you have a similar positive experience, and please reach out if you have any questions about building your website with Jekyll and Netlify.&lt;/p&gt;
        </content>
      </entry>
    </feed>