Page 4

Next.js and Tumblr as a CMS Part 2: Data Fetching

Welp, I thought I could start a series of posts, get married shortly after, and not wind up with a huge delay between them. That was overoptimistic. But at long last, here’s my second post on using Tumblr with Next.js. Check out the first one for background on why Tumblr and thoughts on organizing repos or keep reading for pointers on fetching data (with a healthy smattering of mistakes I made that you should avoid).

The Pages

Assuming you’ve already bootstrapped a Next.js app (if you haven’t, follow the instructions here), the first step is figuring out which pages you’ll need to re-create to match your existing Tumblr. For a basic blog, your pages directory should look something like this:

pages:
  ├── page:
  │   └── [page].js
  ├── post:
  │   └── [...id].js
  ├── tagged:
  │   ├── [tag]:
  │   │   └── page:
  │   │       └── [page].js
  │   └── [tag].js
  ├── 404.js
  └── index.js

If your Tumblr has custom pages, you’ll add those directly under /pages. One thing to keep in mind, the Tumblr API doesn’t support saving user questions or submissions so Next.js might not be the best solution if you need that functionality.

Connecting to Tumblr

We’ll be using the tumblr.js NPM package to connect to the Tumblr API. Install it with:

yarn add tumblr.js

For authentication, you’ll need to get an OAuth consumer key, consumer secret, token, and token secret. Head over to Tumblr and register a new application to get your consumer key and consumer secret. Don’t stress over that form too much, you can ignore any non-required fields.

Once you have those values, enter them in the Tumblr API console and click authenticate to get your tokens. Then copy all four and save them to an .env.local file for use in your Next.js app:

TUMBLR_CONSUMER_KEY=****
TUMBLR_CONSUMER_SECRET=****
TUMBLR_TOKEN=****
TUMBLR_TOKEN_SECRET=****

If you’re wondering why I recommend this method instead of using fetch with an API key, that was one of my early mistakes. Everything was fine until I finished the new site and updated my blog’s visibility (hiding it outside of the Tumblr dashboard). At that point, all my requests started failing because I no longer had the proper permissions. This approach should work even if you change your settings.

Fetching Data

Now to write some actual code!

Start by creating a file called tumblr.js. I keep mine in a utils folder but you can put it wherever you’d like. This file will export a find method that uses the package we installed and the secrets we saved in the last step to create a Tumblr client and request blog posts. Here’s a basic example (swap out “yourtumblr” for your Tumblr):

import tumblr from 'tumblr.js';

const CLIENT = {
  consumer_key: process.env.TUMBLR_CONSUMER_KEY,
  consumer_secret: process.env.TUMBLR_CONSUMER_SECRET,
  token: process.env.TUMBLR_TOKEN,
  token_secret: process.env.TUMBLR_TOKEN_SECRET,
  returnPromises: true
};

export async function find (limit = 10, page = 1, id, tag) {
  const client = tumblr.createClient(CLIENT);
  return await getPosts(client, limit, limit * (page - 1), id, tag);
}

function getPosts (client, limit, offset, id, tag) {
  return new Promise ((resolve) => {
    client.blogPosts('yourtumblr.tumblr.com', { limit, offset, id, tag })
      .then((response) => resolve(response))
      .catch(() => resolve({}));
  });
}

The find method accepts limit, page number, post ID, and tag params so it can be reused across all the Next.js pages listed above.

Static Props & Paths

Using the same find method means writing similar getStaticProps and getStaticPaths blocks for every page so I’ll just run through index.js and post/[...id].js here. If you want more examples, check out the pages directory in my blog repo (although I do a little additional post parsing that I haven’t mentioned yet).

The index.js page uses the default params and is fairly simple:

import { find } from '../utils/tumblr';

...

export async function getStaticProps () {
  const response = await find();

  return {
    props: response,
    revalidate: 3600
  };
}

Things get a little more complicated in post/[...id].js. We’ll be using incremental static regeneration to avoid hitting Tumblr’s 300 API calls per minute rate limit during the build process. (My second early mistake: when I first started, ISG was brand new so I tried other workarounds for limiting requests like adapting this method for caching data globally Although it technically worked, it caused a lot of extra deploys and stale data.)

In the code below, I’m statically generating the most recent 20 posts on my blog and revalidating after an hour (3600 seconds):

import { find } from '../../utils/tumblr'; 

...

export async function getStaticPaths () {
  const response = await find(20);

  return {
    paths: response.posts.map((post) => {
      const params = new URL(post.post_url).pathname.replace('/post/', '').split('/');
      return { params: { id: [ params[0] , params[1] || '' ] }};
    }),
    fallback: 'blocking'
  };
}

export async function getStaticProps ({ params }) {
  const response = await find(1, 1, params.id[0]);

  if (!(response.posts || [])[0]) {
    return { notFound: true };
  }

  return {
    props: {
      post: response.posts[0]
    },
    revalidate: 3600
  };
}

You can play around with the number of posts per page and the time between revalidations to see what works for you. If Tumblr isn’t able to find any posts with the current ID, the 404 page renders instead.

That’s it for now! Up next, sitemaps and RSS.

Next.js and Tumblr as a CMS

Thought I would do a series of posts on moving my blog from a Tumblr-hosted and themed site to a Tumblr powered Next.js app.

You might reasonably ask, “Why would anyone want to do that when there are so many actual CMS options available?”.

In my case, it’s mostly sentimental. Way back when I was deciding whether to switch careers and try coding full time, I took a lot of freelance jobs converting PSDs to Tumblr themes and I’ve been using it ever since.

But, I finally wanted a little more control over my blog and a good excuse to experiment more with Next.js so I decided to mix it up just a little. Let’s dig into some of the decision making, adventures in data fetching, and general idiosyncrasies that come with using Tumblr as a CMS.


How To Set It Up

Starting at the beginning, the very first question was how to set everything up. Where should the site and the code live? How do you share the parts that need to be shared? It seems simple but there are a lot of options.

Keeping with the original Tumblr setup, I decided to create a new app for my blog instead of trying to integrate it with the rest of my site (which was already using Next.js). I wanted to use Vercel for hosting which meant I would just need to update my subdomain records once everything was up and running.

Since Tumblr templates are basically HTML files that include Tumblr’s custom blocks and variables, I’d never really shared much code between my blog and the rest of my site even though they look pretty similar. I had a small script to generate stylesheets but that was it. Using Next.js for both opened up the possibility of sharing React components, variables, and utilities in addition to CSS.

To figure out how to do that, I followed one article (6 Ways to Share React Components in 2020) to another (4 Git Submodules Alternatives You Should Know) and finally landed at Git subtree: the alternative to Git submodule for step-by-step instructions.

Any solution that required publishing components, individually or as a monorepo, seemed like overkill for a smallish, one dev project. Git subtrees provided a way to nest one repo as a subdirectory in others which was exactly what I wanted. I split out shared components like the header, footer, form elements, etc. into a separate repo, ran the commands from the tutorial above in my blog and site repos to hook it all up, and voila. No copying and pasting duplicate code between sections and no publishing to NPM or other third-parties.

So that’s the foundation, stay tuned for the next installment for data fetching and some actual code!

This font is so much fun it makes me want to drop all my in progress projects and spend more time playing around with color fonts. Anaglyph Isometric by Graphic Spirit is an OpenType-SVG font that’ll take you back to the classic, paper 3D glasses. And it’s free for personal use.

Download at Font Space or buy at Creative Market.

Switching things up a little and recommending a commercial use only font. Bushwhack by Vlad Derkach is a quirky, hand drawn serif. It’s all caps but the uppercase and lowercase are unique for more variety.

Buy at Creative Fabrica.

Font Release! Quintet

Quintet Font Poster

Well look at that, it’s a font I made for a change.

Quintet is a narrow, loopy font that tries to capture an old-timey, swing jazzy feel. It also comes with the instrument SVG illustrations I created for the poster images.

It’s free for personal use so go download it to try now. And definitely let me know if you run into any installation issues.

Font Banner - Free Fonts
Advertisement
Font Banner - Free Fonts
Advertisement

React Inner Image Zoom Version 3.0.0

React Inner Image Zoom version 3.0.0 went out earlier this week with a handful of bug fixes, test and build improvements, and one major change.

What’s the big thing to look out for? By popular demand, the imgAttributes prop was added to pass down (almost) any valid React img attributes in a single object instead of as individual props. That means scrSet, sizes, alt, and title are gone but in exchange you get all the data attributes and event handlers you could want. I haven’t submitted updated type definitions to DefinitelyTyped yet but I’ll try to get that done in the next few days.

This release also included a handy new Changelog so I would be remiss not include the official record here:

Changed

  • Replaced srcSet, sizes, alt, and title props with imgAttributes to set the original image’s attributes.
  • Show close button when moveType is set to “drag” on all breakpoints.
  • Switched from setTimeout to onTransitionEnd to check that zoomed image has finished fading out.

Added

  • This handy CHANGELOG.

Fixed

  • Added stopPropagation on touchmove to prevent events below fullscreen modal.

If you run into any bugs, please let me know in the GitHub issues.

A little late but I am going to recommend a font every month this year. 

I like the intentional, handdrawn imperfections in Markisa by Jetsmax Studio. It’s messy but in a good way. The font is free for personal use only but that free version comes with an extra outline variation and all possible characters.

Download at dafont or buy at Creative Fabrica.

All the Fonts I’ve Liked This Year

It’s the end of the year so here’s a roundup of all the fonts I’ve recommended in 2021. I know I’m missing a couple months so here’s hoping for the full twelve in 2022.

January - Jagiq by twinletter

Jagiq by twinletter poster

Download at Font Space or buy on Creative Fabrica.

March - Together of Love by goodigital

Together of Love by goodigital poster

Download at 1001 Fonts or buy on Creative Fabrica.

June - Hot Dog by Toko Laris Djaja

Hot Dog by Toko Laris Djaja poster

Download at dafont or buy on Creative Fabrica.

July - Ducky Manly by Allmo Studio

Ducky Manly by Allmo Studio poster

Download at dafont or buy on Creative Fabrica.

August - Cherish Today by Sarid Ezra

Cherish Today by Sarid Ezra poster

Download at dafont or buy on Creative Fabrica.

September - Black Ground by Letterhend Studio

Black Ground by Letterhend Studio poster

Download at FontSpace or buy on Creative Fabrica.

October - Organic Peach by Prioritype

Organic Peach by Prioritype poster

Download at FontSpace or buy on Creative Fabrica.

November - Javatages by Good Java Studio

Javatages by Good Java Studio poster

Download at dafont or buy on Creative Fabrica.

December - Fontanio by ArdyanaTypes

Fontanio by ArdyanaTypes poster

Download at FontSpace or buy at Creative Market.