AstroJAM StackNetlifyCMS

Author your Astro site's content with Git-based CMSs

Aftab Alam

Aftab AlamFebruary 08, 2022

15 min read–––

Aftab Alam

If you've followed the Learn Astro series, you'd have an Astro site already up and running at Vercel. We concluded with a good, crisp, blog setup and also learned Astro in the process. However, there's one aspect that still feels a bit non-intuitive. We author our content by hand in editors that could support markdown, which could be a bit clumsy. Plus, missing any important front-matter tags could lead to unexpected results. There should be a better way to do this. In this article, we'll cover how we can manage the markdown content like a pro with open-source, headless, Git-based CMS - Netlify CMS.

What's a Git-based CMS?

A Git-based CMS is your day-to-day CMS with just a slight twist - Git as the data source and content manager. Unlike the traditional CMSs that persist your changes in a database, Git-based CMSs are headless and let you

Keep the content management duties separate from the presentational responsibilities
Work with markdown and associated media files directly in their UIs
Update your changes through Git commits on Github (or systems following the same versioning model)
You get all the essential content management features you expect your CMS to ship with, without much hassles of a conventional CMS setup. Most of the CMS configuration is versioned in the same repository(Self-hosted) you use to maintain the data in markdown files.

When it comes to Git-based or JAMStack CMSs in the market NetlifyCMS and Forestry(or TinaCMS) are two of the most popular CMS that can go with git-based content.

NetlifyCMS, by Netlify - the JAMStack Stack provider, is fully open-source, has a good basic experience, and plays well with the overall Netlify suite of tools. The only requirement, I've found with Ink, is that the site must be hosted on Netlify.

Forestry is also pretty good and has more superior content editing experience but you'd be able to work with only a limited amount of sites in the free plan. Config for your CMS is still stored in Github, but Forestry's experience is more controlled.

For the current article, we'll use NetlifyCMS as the CMS of our choice.

Deploy Ink to Netlify

Although not necessary, NetlifyCMS uses certain features that are Netlify-native. Wouldn't it be good if the deployment pipeline is also Netlify-native? Let's go ahead and set up our site on Netlify. Head on over to the site, and use your Github account to signup with Netlify.

Once Signed-up/Signed-in you should land on the Netlify dashboard

Click on the "New Site from Git" to connect Github, and configure a site to work with Netlify

Press Github, and choose [your-gh-username]/[repo-name] from the list of repository it presents. For us, it should be [your-gh-username]/astro-ink if you're working with a cloned template or fork of astro-ink.

Netlify must have access to list your repositories for this to work. You can provide full access or configure it on a per-site basis.

Once you've connected to github and picked the repository, configure the build settings as per your SSG. By default, Netlify picks the public directory for SSG-generated content, but since Astro prefers a dist directory for the same, you should change the publish directory to /dist. The command to build must be yarn build

Once done, click on the "Deploy" button.

Netlify will take you to the project dashboard, where you can see your site deployment in progress. Once done, you should see a random-app-name.netlify.com available.

Your app's hosted now on Netlify. If you want you can change the random name to something more appropriate like astro-ink.netlify.com.

Let's configure the Netlify CMS

NetlifyCMS is a React SPA fundamentally. To make it work, you need to link the CMS file from the CDN and make the index.html that hosts it available in the public directory of your SSG, alongside other public assets you serve.

For us, /public is the directory Astro uses to host static, non-generated, assets. We'll use the same to host the NetlifyCMS.

Setup NetlifyCMS

Let's consider that We eventually want the NetlifyCMS/CMS to be available at astro-ink.netlify.com/admin. For this to work, we'll go into the public directory and create a folder called admin.

Within the admin directory we first need the index.html file that will render the NetlifyCMS when the author will visit astro-ink.netlify.com/admin

Create an index.html file and place the following content

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Astro Ink - Content Manager</title>
  </head>
  <body>
<script src="https://unpkg.com/netlify-cms@2.10.161/dist/netlify-cms.js"></script>
</body> </html>

This will include the netlify-cms scripts that manage the rendering for NetlifyCMS.

Configure NetlifyCMS

With the script included, now we need to tell Netlify about our CMS configuration and where to find it.

Create a config.toml(a variant of .yml) and put the following

config.toml
backend:
  name: git-gateway
  branch: main

This tells NetlifyCMS to use the git gateway and the main branch to push the commits.

Netlify CI/CD is pre-configured to build your site with every commit and this behavior is at the center of content editing with a Git CMS. Every change you do in a Git-based CMS pushes a new commit to your repo, which triggers a rebuild of your site. Here we're configuring Netlify to use the main branch for all the commits.

Configure the Content types

With the CMS setup, the next thing to do is to configure the content types we'll allow our users to create and update. With NetlifyCMS we manage the configuration in the same Github repository's config.toml file we just updated for Git in the previous step. Go ahead and add the following changes.

config.toml
collections: # A list of collections the CMS should be able to edit
  - name: 'post' # Used in routes, ie.: /admin/collections/:slug/edit
    label: 'Post' # Used in the UI, ie.: "New Post"
folder: 'src/pages/blog' # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection fields: # The fields each document in this collection have - {label: "Layout", name: "layout", widget: "hidden", default: "$/layouts/post.astro"} - { label: 'Title', name: 'title', widget: 'string' } - { label: 'Description', name: 'description', widget: 'text' } - { label: 'Body', name: 'body', widget: 'markdown' } - { label: 'Tags', name: 'tags', widget: 'list' } - { label: 'Author', name: 'author', widget: 'string' } - { label: 'Author Twitter Handle', name: 'authorTwitter', widget: 'string' } - { label: 'Publish Date', name: 'date', widget: 'datetime' }

Here's what all the above settings mean to NetlifyCMS

  • collections - Collections are all the content types your CMS is configured to edit. For us, all the posts inside the /src/pages/blog followed the same front-matter structure being a blog with similar attribute requirements. Collections are the blueprints or classes for your content type. With new content types, you add new collections with all the config that makes them unique and that you'd like to edit.

There's just one post type we'd like to edit for our CMS, so we'll just add one entry named post under collections

  • name - The unique name the collection will be recognized by in the NetlifyCMS system
  • label - The label your collection will be recognized by in the UI
  • folder - The location in your Github repo where all your markdown files will be kept. Since src/pages/blog is the folder, we've used to keep the hand-edited markdown file and our blogs are available at site/blog/[slug], we'll use src/pages/blog as the folder.
  • create - boolean value to inform NetlifyCMS if creation is allowed
  • fields - fields configures all the fields that we'd prefer to be editable in the NetlifyCMS. They can be directly mapped to the front-matter details we're maintaining with *.md file, and the UI controls that are more appropriate to edit it conveniently.

Since we had the following front-matter structure

---
layout: $/layouts/post.astro
title: Introducing Astro - Ship Less JavaScript
date: 2021-06-08
author: Fred K. Schott
authorTwitter: FredKSchott
category: design
tags:
- Astro
- jam-stack
description: There's a simple secret to building a faster website — just ship less.
---

it could get translated to

- {label: "Layout", name: "layout", widget: "hidden", default: "$/layouts/post.astro"}
    - { label: 'Title', name: 'title', widget: 'string' }
    - { label: 'Description', name: 'description', widget: 'text' }
    - { label: 'Body', name: 'body', widget: 'markdown' }
    - { label: 'Tags', name: 'tags', widget: 'list' }
    - { label: 'Author', name: 'author', widget: 'string' }
    - { label: 'Author Twitter Handle', name: 'authorTwitter', widget: 'string' }
    - { label: 'Publish Date', name: 'date', widget: 'datetime' }

in config.yml. As learned earlier, name is for NetlifyCMS and label is for you. widget is what controls what UI element needs to be rendered in NetlifyCMS for each field you configure. NetlifyCMS supports a wide range of widgets you can use here to render a control as you prefer. You can even create your own custom widgets if you want.

With the above changes, the config.yml file should look like

config.yml
backend:
  name: git-gateway
  branch: main

collections: # A list of collections the CMS should be able to edit
  - name: 'post' # Used in routes, ie.: /admin/collections/:slug/edit
    label: 'Post' # Used in the UI, ie.: "New Post"
    folder: 'src/pages/blog' # The path to the folder where the documents are stored
    create: true # Allow users to create new documents in this collection
    fields: # The fields each document in this collection have
      - {label: "Layout", name: "layout", widget: "hidden", default: "$/layouts/post.astro"}
      - { label: 'Title', name: 'title', widget: 'string' }
      - { label: 'Description', name: 'description', widget: 'text' }
      - { label: 'Body', name: 'body', widget: 'markdown' }
      - { label: 'Tags', name: 'tags', widget: 'list' }
      - { label: 'Author', name: 'author', widget: 'string' }
      - { label: 'Author Twitter Handle', name: 'authorTwitter', widget: 'string' }
      - { label: 'Publish Date', name: 'date', widget: 'datetime' }

Pay attention to the layout field. It's necessary to register it as a hidden field so that program-level concerns can be hidden from the author's eye, and accidental, unintended mistakes can be prevented.

Configure NetlifyCMS for media uploads

textual content would not be the only format authors would use. We may add images to our posts. To support images, we'll have to tell NetlifyCMS where it can upload/find images with

config.yml
media_folder: 'public/images/uploads' # Folder where user uploaded files should go
public_folder: '/images/uploads'
  • media_folder - Where should the user uploaded files go?
  • public_folder - The location to use to link the user uploaded files.

The resultant config.yml should look like

config.yml
backend:
  name: git-gateway
  branch: main

media_folder: 'public/images/uploads' # Folder where user uploaded files should go
public_folder: '/images/uploads'
collections: # A list of collections the CMS should be able to edit - name: 'post' # Used in routes, ie.: /admin/collections/:slug/edit label: 'Post' # Used in the UI, ie.: "New Post" folder: 'src/pages/blog' # The path to the folder where the documents are stored create: true # Allow users to create new documents in this collection fields: # The fields each document in this collection have - {label: "Layout", name: "layout", widget: "hidden", default: "$/layouts/post.astro"} - { label: 'Title', name: 'title', widget: 'string' } - { label: 'Description', name: 'description', widget: 'text' } - { label: 'Body', name: 'body', widget: 'markdown' } - { label: 'Tags', name: 'tags', widget: 'list' } - { label: 'Author', name: 'author', widget: 'string' } - { label: 'Author Twitter Handle', name: 'authorTwitter', widget: 'string' } - { label: 'Publish Date', name: 'date', widget: 'datetime' }

With all the above changes done, let's push our changes to Github.

git add .
git commit -m "feat: Add Netlify CMS Admin"
git push

Configure Netlify for Authentication and Git Backend

CMS configuration is just one part of the CMS story. For your users to log in to your site with their Git credentials you'll require a way to identify/authorize them. Netlify Identity is Netlify's answer to browser-based user authentication, and configuring it with Netlify is utterly simple. Being Netlify's own, It's also just a click away in the Netlify dashboard.

In your Netlify dashboard click your site settings -> https://app.netlify.com/sites/[your-ink-clone]/settings/general and then click on Identity https://app.netlify.com/sites/[your-ink-clone]/settings/identity. Under the Identity section click on Enable Identity.

For registration preferences https://app.netlify.com/sites/astro-ink/settings/identity#registration you can leave the Open radio button selected and click on Save

Enable Git Gateway

Under the identity section, scroll to Services https://app.netlify.com/sites/[your-ink-clone]/settings/identity#services and enable Git Gateway. This will enable the NetlifyCMS to work with Git as a backend.

Load/Initailize the Identity files

For Netlify Identity to work with your NetlifyCMS rendering page /admin/index.html we'll have to hook up the identity script

https://identity.netlify.com/v1/netlify-identity-widget.js

from the CDN in the head tag of public/admin/index.html

public/admin/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Astro Ink - Content Manager</title>
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
</head> <body> <script src="https://unpkg.com/netlify-cms@2.10.161/dist/netlify-cms.js"></script> </body> </html>

This covers the /admin relative URLs. To have Netlify Identity in action on all the site pages the same needs to be done on all the site pages. Open src/components/BaseHead.astro and add the following on line 64

<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>

One final change is redirecting the users to CMS Admin UI after successful login. For that, to work we'll be required a way to know Netlify Identity is available/initialized, as soon as the document is done loading, and redirect the users on successful login. Since we have an Astro site, and its hydration on JS behavior is opt-in we'll create a component to register this behavior that will get triggered on window load

src/components/NetlifyIdentity.svelte
<script lang="ts">
    import { onMount} from 'svelte'
    onMount(() => {
        if (window.netlifyIdentity) {
            window.netlifyIdentity.on('init', (user) => {
                if (!user) {
                    window.netlifyIdentity.on('login', () => {
                        document.location.href = '/admin/';
                    });
                }
            });
        }
    })
</script>

Open src/components/Footer.astro and include the above component

src/components/Footer.astro
---
    import { SITE } from '$/config'
    import ModeLabel from './ModeLabel.svelte'
import NetlifyIdentity from './NetlifyIdentity.svelte'
---
<div class="footer">
    <nav class="nav">
        <div>2021  &copy; Copyright notice |  <a href={ SITE.githubUrl } title={`${ SITE.name }'s Github URL'`}>{ SITE.name }</a>
        <ModeLabel client:load/> theme on <a href="https://astro.build/">Astro</a></div>
<NetlifyIdentity client:load/>
</nav> </div>

Now let's push our changes, and open the [your-site].netlify.app/admin to see the CMS in action. Commit: https://github.com/one-aalam/astro-ink/commit/2b39e1519906162ec05abcadd2d08f8849961de2

Work with NelifyCMS

Click on the "Login with Netlify Identity" button, https://astro-ink.netlify.app/admin/#/

and sign-up with your actual details(name, email, password, etc.) or continue with Github credentials for the first time. Once signed up with basic credentials or Github, and the account is activated, use "Login" to visit the CMS dashboard in the subsequent visits.

You'll be landed to the default collections view post a successful login like https://astro-ink.netlify.app/admin/#/collections/post

Where you can Edit the pre-existing posts, or create new posts and publish them. Go ahead and create/update/delete posts. Once done, click on "Publish" and choose one of the following options

  • Publish now
  • Publish and Create new
  • Publish and duplicate

and see the NetlifyCMS sync the changes with your Github repo. Since Netlify CI is watching your Github repo for commits, it will re-build your site as soon as it finds a new commit with your content changes.

One important thing you must keep in mind is that the NetlifyCMS content-editing uses the same commit-based workflow you do to commit your code. Since the application code and content changes are inseparable, you'll have to keep your local changes always in sync with the remote repo to not have conflicts.

Conclusion

In this article, you got to learn about Git-based CMSs and Netlify. Git-based CMSs offer several benefits like -

  • Simplicity of setup, configuration, and management
  • version controlled goodness, without a complex pipeline or infrastructural requirements, and thus rolling back is easy
  • No vendor lock-in as all content is present as flat files. You can use any tools that can work natively with Markdown. If you want you can additionally set up Forestry.io also, or any other Git CMS.
  • Natural and homogenous to how we code. Use a tool, or don't use it you can still get quite done. Less lock-in leads to less hassles when migrating or changing authoring tools.

but there are certain features only pretty advanced CMS can capably do.

  • If markdown isn't your source of truth, you cannot use the approach to scale to other sites or repositories. Every markdown site will need exactly one CMS
  • The CMS might not be very capable to handle a humongous amount of content, because they are pretty simplistic by nature.
  • Content Modelling is one-one mapped to your front-matter structures, so they're pretty coupled in that sense. The models/collections are content-derived, and can only be extended to support what you can and must keep in markdown. Also with too many collections, it could get pretty involved
  • Your media uploads are restricted by the max size Git allows for a certain repo

But even with the aforementioned limitations, having an option to co-locate content and code, in such a seamless manner makes the job of managing simple sites and blogs effortless.

Thank you for being till the end 🙌 . If you enjoyed this article, or learned something new, please take a second to tweet this article or share on LinkedIn so others can discover it. Or add to the discussion on Twitter.

Tweet about this post(by clicking the button above) or like/re-tweet one of the tweets that shares this article, and it will show up here eventually