Jonnie Hallman is a designer and developer based in Brooklyn, working on stripe.com at Stripe.

His work focuses on building thoughtful, intuitive, and delightful interactions for the web, with a devotion to process, transparency, and sharing what he learns.


After adding the intro to the site, the plaintext <h1> felt strange—too much text of the same style without much variation in hierarchy. I’ve been planning to replace it with something else, but nothing screamed out to me. Then, I revisited the existing version of my site, which uses a simple square “DT” logo.

2016.destroytoday.com logo

Honestly, I still really like this little tag—especially how its color continues above the page when you pull it down. Instead of spinning my wheels trying to think of another option, I decided to keep the logo. Redesigning a site doesn’t mean you need to start from scratch with everything. If a part of the previous design still holds up, use it.

When carrying over the logo from the other codebase, I did find a couple ways to improve it. For one, the SVG didn’t have a <title> tag, so the logo wouldn’t be described well for screen readers, or display a tooltip when hovered. I also took this opportunity to make use of CSS variables to assign the color of the logo and background above the fold. Now, when I want to specify a different color, I simply need to set --accentColor on the :root selector. For now, I hardcoded the value as blue to add any sort of accent color to the page. I refrained from picking a really nice color upfront because I want to encourage myself to replace it when I’m ready to consider all of the site’s colors at once.


I’ve been dying to make progress with the site as a whole, rather than only improving upon the blog portion, so I decided to take a simple first step by adding an “intro” to the top of the site. This is my first piece of content outside of the blog stream, which means I needed a new content model in Contentful. Since the content itself could be styled using the tags within the Rich Text editor, I created a Copy content model with only a body Rich Text field and an identifier slug field for referencing.

In my Nuxt view’s asyncData, I now needed to make two requests—one for the article entries and another for the intro. I searched around for to handle this, and found that instead of returning the Promise from the article entries request, I could make the method async, and await Promise.all([...]) with the two requests. Then, I simply return the data I need to use in my template.

Like the Article content model, I also made a Vue component for the Copy content model that renders the Rich Text field. Along with providing a reusable component, this gave me an opportunity to refactor my Article component, which also renders a Rich Text field. Instead of duplicating the code, I simply replaced the Article body with the Copy component.

Now that I took the first step outside the blog, I now have momentum to continue to other parts of the site. I’m not sure what I’ll tackle next, but it feels good to have the intro—even if the copy itself is still in flux.


I didn’t realize until a friend nudged me about it, but I’ve been using Nuxt’s default favicon this whole time. There’s not much to a favicon, and I didn’t want to overthink it, so I simply carried over the “transparent canvas” favicon I made from my existing site. I did set it up in Figma as its own artboard in an attempt to organize my assets better, so there’s that.

RSS feed location

Previously, I served the site’s RSS feed /feed.xml, but despite having a proper meta link tag pointing to it, some RSS apps still couldn’t discover it. This made me realize that /feed.xml might not be as intuitive as it could be, so I initially changed the feed location to /rss, which feels easier to check and maybe more standardized? Then, as I was browsing the website of my good friend, Chris Shiflett, I realized he publishes multiple RSS feeds, located at /feeds/blog and /feeds/links to separate blog posts from links. I love this idea because it immediately makes me think about how folks might want to only subscribe to certain parts of my site.

Instead of restricting myself to the one feed, I opened up the possibility of multiple feeds in the future by following suit. Since the current feed is essentially for the entire site, I’m now serving it from /feeds/all, but still redirecting /rss to that location. Then, once I add more content types, like long-form blog posts or new portfolio pieces, I’ll create feeds for those, but still list them in the catch-all feed, too.

Along with making the site more future-proof, this change also gave me the opportunity to set up redirects, since existing subscribers would need to land on the new endpoint. Setting up the redirect was pretty straightforward with the @nuxt-community/redirect-module middleware, but I don’t love installing yet another stack-specific dependency simply to handle redirects compared to a system-level config. It’ll do for now.

I’m starting to think about routes for the rest of the site, which is why I wanted to set up the system for redirects. I’d like to move on from only having articles, which I think will help expose other scenarios to consider, like navigation. Every small change sprouts an entirely new branch of considerations to make. Good thing I enjoy building websites.


TypeScript is one of those technologies that I missed the boat with early on, so I didn’t feel compelled to follow along with it. Since then, a large majority of the dev community went full-in, which makes it hard to ignore now, so I’d be foolish not to see what the fuss is about—at the very least, I’ll learn more about TypeScript, so I have more context and experience when people talk about it. Also, this site is still early enough in development that TypeScript is much easier to implement now, and either embrace or reject, than attempt a complex rewrite later.

For those who are unfamiliar with TypeScript, it is described as “JavaScript that scales ... a typed superset of JavaScript that compiles to plain JavaScript.” TypeScript brings static type-checking to JavaScript that will catch any type-related issues during compile time—saving you from runtime errors. This means instead of writing a function:

function fooToBar(str) {
  return str.replace(/foo/g, 'bar')

fooToBar(1337) // Runtime error: str.replace is not a function

...and running the risk of someone passing a non-string argument, I could write:

function fooToBar(str:string) {
  return str.replace(/foo/g, 'bar')

fooToBar(1337) // Compiler error: Argument of type '1337' is not assignable to parameter of type 'string'

...which would throw an error at compile time if you tried to pass something else. While this also makes it easier to understand unfamiliar code by documenting props and responses, TypeScript also enables code completion and IntelliSense for compatible IDEs, like VS Code. This lets you hover a method or argument to see more info about it, but also indicates the compiler error in the IDE as well.

I am cautiously approaching TypeScript with a strong bias. After CoffeeScript, I’m pretty scarred when it comes to writing invalid JavaScript and adding another layer to the build process. The beauty of a personal website, however, is that it’s the perfect opportunity to experiment with technologies like this. A lot of this site’s stack is overkill, but this site gives me the opportunity to learn new things in a safe environment. If an experiment doesn’t work out, I can easily remove it and move on—compared to an early stack decision with an app that might come back to haunt you forever. *shakes fist at Angular & CoffeeScript*

Sideways progress

Last night, in a moment fueled with creative energy, I sat down to making forward progress with the site. I didn’t know exactly what I wanted to work on, so I started looking for the next most valuable thing to work on. In doing so, my eye caught a seemingly small issue with the RSS feed—it was rendering the articles without any of my custom renderers, so any images or hyperlinks appeared as Contentful IDs. This definitely felt worth prioritizing first, and didn’t seem like a time sink. I’d simply extract my renderers out of the Article component, so they could be used by both the Article and the RSS feed, which was on my to-do list anyway.

I approached the refactor by keeping each custom renderer in a /lib/contentful/renderers folder, in their own JavaScript file, with each exporting a render function. Then, I mapped these methods to the Contentful documentToHtmlString options for a much cleaner, predictable design—rather than keeping all the render functions inline. This made the renderers easier to find, straightforward to build, and well-isolated to test if I wanted to.

The refactor felt great and didn’t take too long, so I pushed the change, and checked the deploy—not expecting anything out of the ordinary. Instead, I found that none of the articles in the production RSS feed rendered. The RSS feed looked fine locally, but on production, it only displayed the feed metadata—no error or anything in the logs. Something told me this was going to consume my night. It did.

Since the bug itself was in production, I could only test it by deploying, which took nearly a minute each time. I started by double-checking that there were articles in the feed—there were. Next, I checked the output of my extracted render method with all the custom renderers—empty. I then tried wrapping the render in a try { ... } catch to see if any errors were coming through, but being silenced for some reason—yes, but the returned “error” was an empty object.

I knew the issue was with my custom renderers, but couldn’t put my finger on it, so I took the most technically advanced approach I could think of—commenting out code and uncommenting it. I started with all the renderers, then re-added them one-by-one until I discovered the culprit—my <code> renderer. This was a surprise because there’s not much to it. The renderer simply checks for the syntax language using the regular expression I wrote about in a previous article, then uses Prism.js to highlight the code.

I continued to comment out code throughout the render function until I could easily toggle the bug, which existed in the following lines of code, which were responsible for extracting the specified language from a code block:

const match = code.match(/^(?<language>\w+):`(?<code>[\s\S]+)`$/)

if (match) {
  language = match.groups.language
  code = match.groups.code

I spent a few minutes scratching my head until it dawned on me—is there a chance my production environment differs from my local dev environment? *gasp* No... It couldn’t be... We live in an age where everything just works... I nervously added console.log(process.version) to check the Node version. Locally, it was latest recommended version, 12.13.1, but in production, my Now environment used Node 8, which doesn’t support named groups in regular expressions. I was relieved to find the issue, but any sort of clue would’ve saved me a couple hours.

To fix the issue, and maintain consistency between my local and production dev environments, I specified the target Node version in my package.json:

  "engines": {
    "node": "12.13.1"

I also added a .nvmrc file to my codebase to specify the version, so I could run nvm use to use the right Node version locally. I’d love to have it specified in one place, but Now only seems to use the version from my package.json. I didn’t spend long looking for a way, but I’m sure there must be one.

I typically get down on myself for experiences like this because I often see it as lost time that could’ve been spent towards forward progress instead of troubleshooting an issue. At the same time, I accomplished a few things. I mirrored my RSS feed to the actual site; I refactored my renderers to be easier to navigate and test; and I synchronized my Node version between local dev and production—avoiding an inevitable head-scratcher down the road. I’m still a bit bummed that I spent so long troubleshooting, but that’s the life of a dev.


Now that I’ve written more than a dozen articles, this page is starting to feel incredibly long, which makes it the perfect time to think about pagination. In Contentful, the API request I use to retrieve entries takes skip and limit parameters to paginate the results. In an effort to embrace the metric system (while biting my thumb at the imperial system), I’ve limited pages to 10 articles. I originally considered a classic sidebar of month-based categories (alongside a blogroll of my friends), but didn’t want to fall victim to the 1-article months that shame you for not writing as consistently as you thought you would.

To persist the pagination, I’m using a 1-based page query parameter. Then, in my Contentful request, I set the skip parameter to ($route.query.page - 1) * limit. Since the site is universal, navigating between pages is lightning quick, which makes me wonder if it’s worth implementing a smoother transition at some point. I typically hate transitions between pages, but this might be on opportunity to improve the UX rather than snapping to the top. At the very least, this initial barebones implementation provides plenty of room for tweaking.


In the previous article, I linked to another article for the first time. With Contentful, you can link to another entry by setting the link type to “entry” instead of “url”. This lets you select another entry in your account, but by default only renders the payload of the entry because Contentful relies on you to handle routing (and render the anchor element).

Similar to images, this required me to create a custom renderer for the link. For now, I know that any link within the site will go to another article, so I simply hard-coded the routing, but eventually, I’ll need proper logic to figure out the right path for a given entry. At that time, I’ll most likely route entries based on their content type—articles to /journal/:slug, portfolio items to /work/:slug, etc.

I do wonder if I’ll prefer a single blog stream for quick journal articles, traditional blog posts, and anything in-between. That would make it easier than checking several different spots, while still providing separate RSS feeds based on type.


After writing a few articles, a friend “Well, actually‘d” me about one of them. In the article, I suggested that Contentful should use a prefix for their API tokens, so you wouldn’t need to specify a different host between dev and production. I didn’t realize that Contentful’s “delivery” API, which is intended for production, is served through a CDN, while the “preview” API isn’t. I wanted to point this out on the original article, but I didn’t want to lose the original content. At first, I considered “highlights”, which could be hovered to show a tooltip, but it didn’t feel accessible enough, so I decided to lean on another pre-web writing element—the footnote.

In Contentful, there’s a “Rich Text” content model type that’s common for article bodies, like the text you’re reading now—potentially including bold, italic, and underlined text, or links, images, and code snippets. Within this rich text, you can embed assets, other entries, or inline entries.

Inline Entry

While regular embedded entries make up an entire block of content, an inline entry can live inline with your text—similar to the inline code snippets I’ve written in other articles, but existing as a referenced entry rather than embedded as text. With an inline entry, I was able to highlight the original words I needed to reference with a custom renderer that simply wraps the text with a <span> and adds a <sup> for the footnote number. Here is an example footnote reference.

*American Psycho voiceover* Oh, the background color? That’s moccasin.

The footnote number is tricky because I don’t have any context in the custom renderer. Typically, I would simply use the index number of the footnote if I had access to it. I still thought this was the most straightforward and least clever approach, so I cheated a bit to make it work. In my Vue component for the article, I created a mounted hook, looped through the <sup> elements, and set their textContent to one plus the index. Since I’m using Nuxt, and serving the site in “universal” mode, the number is still server-side rendered.

To display the footnotes below the article, I filtered the body payload for the footnote entries. Since these are inline, this means iterating through all content arrays within the body field, which definitely doesn’t feel performant, but it works, and I couldn’t easily find another way, so it’ll suffice for now. The request I get from Contentful does return an includes array with the footnote entries, but when retrieving multiple articles, I couldn’t find any references from the footnotes to the related articles, so there doesn’t seem to be a way to match them.

Now that I had the footnotes listed below the article and the references highlighted within the article, I could’ve stopped there, but I didn’t. While searching through footnote implementations, I came across this Daring Fireball article from 2005 that describes John Gruber’s approach of linking references to their footnotes and vice versa. Linking the reference is easy, by simply linking the footnote reference number, but for the footnote itself, John suggests using a “leftwards arrow with a hook” (↩, or &#x21A9;) that links back to the reference.

He also prefixes the anchors with “fn1” and “fnref1” for the footnote and reference, respectively, then includes the footnote number. I didn’t love the abbreviations or the use of the footnote numbers (in case they changed), so I prefix with “footnote” and “reference”, then append the Contentful UUID for the footnote entry. I don’t love using such a long ID string, but it’s unique to the footnote, which is worth more to me.

  1. Here is an example footnote.


While I’m tweaking typography, I figured I’d take the opportunity to further improve readability with more vertically-spaced type. Prior to adding a max-width, I didn’t immediately recognize the sub-par leading because there was so much else “wrong” with the readability, but as soon as I capped the width of the paragraphs, the tight leading stood out like a sore thumb.

By default, line-height is set to normal, which is roughly 1.2. This gives a 16px font a line-height of ~19.2—pretty tight for body copy. Before doing any research, like I did with the max-width, I tried increasing the line-height a tenth until it felt right, which was 1.5. This felt spacious enough, and resulted in a 24px line-height, which satisfies my bias towards base-8 numbers.

To back up my intuition, I did a quick search, and found that the answers vary, but typically land between 1.3 to 1.5. I also asked my studio mate, Frank, who specializes in type. He said, “It looks like you’re happy with 1.5, so go with that.” Forever wise, that Frank. The change meant updating single value, but the result makes the world of difference. Slowly but surely, the site is starting to look real.