Published on 5 January 2023 by Andrew Owen (7 minutes)
I launched the current version of my website a year ago. Having become a developer advocate in 2021, I didn’t think a WordPress site that hadn’t been updated in a decade would cut it any more. I wanted to do something a bit more modern. At my previous company, I’d built a developer portal on Hugo. The company ended up hosting the site itself, but I’d had discussions with Netlify and Forestry at the time. And I’d been using GitHub for my big open source projects for a long time. I picked a free Hugo starter theme from Themefisher that had built-in support for Netlify and Forestry. I spent a weekend on it: setting up the site structure, customizing the theme and adding content. I didn’t have all the features at first (search, tags and RSS came later), but it was a huge step up from my old site.
According to Jamstack, Vercel’s Next.js with its React templates has overtaken Hugo in popularity. And I’m not surprised. By most measures, JavaScript has never been out of the top 10 programming languages over the last two decades. And of 347 static site generators listed, 130 are written in JavaScript, 51 are written in Python and 26 are written in PHP. Hugo, and 16 others, are written in Go. But Hugo claims to be the fastest (you can check out those claims with PageSpeed) so I’m sticking with it. However, since 2019 the team behind Forestry have been developing the next iteration called TinaCMS. And on November 8, 2022 it came out of beta. Forestry is scheduled to be discontinued in late March 2023. Existing users will be offered a migration path to TinaCMS, a next generation headless CMS from the creators of Forestry. There are plans to share the migration tool in mid-January, but I decided to go ahead and do a manual migration.
I decided to take the opportunity to do some clean up and add a Hugo shortcode for audio (thanks to John Arrroyo for information on how to do that). I also finally got around to adding a custom 404 page. I created a new empty GitHub repository and connected it to a new staging site on Netlify. I checked out a local copy and brought in the content from the old version of the site. You can run TinaCMS locally, so after installation I was able to make changes before pushing to staging. I removed the Forestry config (although it wouldn’t have done any harm to leave it in place). But I’ll assume you want to simply add TinaCMS support to your existing repo.
You’ll need npm. If it’s not already installed:
brew install npm
.sudo apt install npm
.scoop install npm
.You also need hugo. You can install it the same way you installed npm.
npx @tinacms/cli@latest init
.npx tinacms dev -c "hugo server -D -p 3003"
.Because I used VScode, I created a tasks.json
file in the .vscode
folder to automate deploying TinaCMS locally:
{
"version": "2.0.0",
"tasks": [
{
"label": "start TinaCMS",
"type": "shell",
"command": "npx tinacms dev -c \"hugo server -D\"",
"group": {
"kind": "build",
"isDefault": true
},
},
]
}
By default, TinaCMS expects to find images in a media
folder. Edit your TinaCMS config file to point to /static/images/
or wherever you keep your images. For example:
media: {
tina: {
mediaRoot: "images",
publicFolder: "static",
},
Before you can edit your content, you need to model it, based on the metadata you’re using in the headers of your markdown files. In my case I’m using date
, description
, draft
status, image
, a tags
list and a title
. Besides the metadata, you also need to define the body
text. My schema looks like this:
schema: {
collections: [
{
name: "blog",
format: "md",
label: "Blog",
path: "content/blog/",
defaultItem: () => {
return {
draft: true,
}
},
fields: [
{
name: "draft",
type: "boolean",
label: "Draft",
required: true,
},
I have a single collection called Blog
that corresponds to the folder where my articles go (content/blog
). The draft
field is a Boolean that determines if the article is displayed. You can set a default value for new articles in the defaultItem
list so that new articles are all created as drafts.
{
name: "title",
type: "string",
label: "Title",
isTitle: true,
required: true,
},
{
name: "date",
type: "datetime",
label: "Date",
},
{
name: "description",
type: "string",
label: "Description",
},
{
name: "image",
type: "image",
label: "Image",
},
The title
, date
, description
, and image
are all fairly self-explanatory. You can set the required
value to true
to prevent saving an article that’s missing a required field.
{
name: 'tags',
type: 'string',
label: 'Tags',
list: true,
},
{
name: 'body',
type: 'rich-text',
isBody: true,
label: "Body",
templates: [
{
name: 'shortcode',
label: 'shortcode',
match: {
start: '{{',
end: '}}',
},
fields: [
{
// Be sure to call this field `text`
name: 'text',
label: 'Text',
type: 'string',
required: true,
isTitle: true,
ui: {
component: 'textarea',
},},],},],},],},],},
The tags
are a set of text strings. For items like this, set the list
value to true
. Setting the type
to rich-text
enables the GUI editor for the body text. To be able to include Hugo shortcodes, you need to include the above template for them. In practice, I found the need to include the opening and closing angle brackets in the start
and end
items so that no space would be inserted between the curly brackets and the angle bracket (because a space kills the audio shortcode).
This part is a headache, but until there’s a Forestry to Tina migration tool, there’s no getting around it. The big problem I encountered was that all my Markdown front matter was in TOML and, at the time of writing, TinaCMS only supports YAML. I used search and replace in VScode. But there is a better way.
Production Content Token
.main
. Then click Create Token.token
you just created.clientID
and token
to your config: export default defineConfig({
branch,
clientId: "", // Get this from tina.io
token: "", // Get this from tina.io
In your netlify.toml
file, add TinaCMS to your build command:
[build]
publish = "public"
command = "yarn tinacms build && hugo"
You can set this directly in your build settings on Netlify, but whatever is in the file will override whatever is on Netlify.
Now you can push your changes to GitHub. After Netlify deploys your build, you’ll be able to work on your site in Tina Cloud.
Before you start editing. Go to Media Manager, click Sync and then click Sync Media. This copies media assets from the images
folder in your designated branch (typically main) in your git repository to Tina Cloud’s asset service. Now you can use those assets in your site with Tina Cloud.
I wrote this before TinaCMS published its Forestry migration tool and guide. I’m indebted to Forestry CEO and co-founder Scott Gallant for sharing a draft of that guide with me when I got stuck (and to JP O’Halloran for writing it). He was also super helpful on the TinaCMS Discord. Fun fact: Tina is named after the llama in “Napoleon Dynamite”.
Also, you may want to decommission your Forestry site. You can do that in Forestry by navigating to My sites. Click the branch drop down and select Remove Site. Then click Remove Site to confirm.