Manage your Astro site's content with Git-based Netlify CMS
Tags: javascript, jamstack, astro, netlify
Published: 2022-02-08 15:18:43

I have a series on my blog [Get Up & Running with Astro](https://aalam.in/blog/astro-get-up-and-running) that teaches you all the things you need to get started with [Astro](https://astro.build/), one of the fastest SSG with a really amazing approach to partial hydration.

If you'll follow the [series](https://aalam.in/blog/astro-get-up-and-running), you'll have an Astro site already up and running at [Vercel](https://astro-ink.vercel.app/). It concludes with a good, crisp, blog setup. 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](https://www.netlifycms.org/) 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


![Netlify Dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oe0x3za8akelty8w4q0f.png)


Click on the _New Site from Git_ to connect Github, and configure a site to work with Netlify


![Connect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uc18jtgnfbembge8pune.png)


![Connected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsuwubdzdi5e98f7akx9.png)

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`.


![Select Repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s28wims987eb2i63ivf9.png)

> 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`


![Configure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9al55ug7fcko5e2h334.png)

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.


![App Available](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tfh127fcgrmgn60xyoq.png)

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.


![Change Site Name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlxdkuum01et4qopu36w.png)

## 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

```HTML{9}: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
```yml: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.

```yml{4}: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
```yml
---
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
```yml
- {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](https://www.netlifycms.org/docs/widgets/#common-widget-options) you can use here to render a control as you prefer. You can even create your own [custom widgets](https://www.netlifycms.org/docs/custom-widgets/) if you want.

With the above changes, the `config.yml`  file should look like
```yml: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
```yml: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
```yml{5-6}: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.
```bash
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.


![Enable Git Gateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9qg2x1jnjz8qo7gsvusd.png)


![Enabled Git Gateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpbhigaabd2dq8l6dkxo.png)


### Load/Initialise 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
```html
https://identity.netlify.com/v1/netlify-identity-widget.js
```
from the CDN in the `head` tag of `public/admin/index.html`

```html{7}: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
```js
<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 behaviour is opt-in we'll create a component to register this behaviour that will get triggered on window `load`

```ts: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

```ts{4}:src/components/Footer.astro
---
    import { SITE } from '$/config'
    import ModeLabel from './ModeLabel.svelte'
    import NetlifyIdentity from './NetlifyIdentity.svelte'
---
```
```html{5}
<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/#/


![CMS Signup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8iedl7n6pwozbpx2756b.png)

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`

![Netlify CMS Admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xr9ktkce0lmbngxfxh68.png)

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.