Friends, I’ve been on the hunt for a decent content management system for static sites for… well, about as long as we’ve all been calling them “static sites,” honestly.
I know, I know: there are a ton of content management system options available, and while I’ve tested several, none have really been the one, y’know? Weird pricing models, difficult customization, some even end up becoming a whole ‘nother thing to manage.
Also, I really enjoy building with site generators such as Astro or Eleventy, but pitching Markdown as the means of managing content is less-than-ideal for many “non-techie” folks.
A few expectations for content management systems might include:
- Easy to use: The most important feature, why you might opt to use a content management system in the first place.
- Minimal Requirements: Look, I’m just trying to update some HTML, I don’t want to think too much about database tables.
- Collaboration: CMS tools work best when multiple contributors work together, contributors who probably don’t know Markdown or what GitHub is.
- Customizable: No website is the same, so we’ll need to be able to make custom fields for different types of content.
Not a terribly long list of demands, I’d say; fairly reasonable, even. That’s why I was happy to discover Pages CMS.
According to its own home page, Pages CMS is the “The No-Hassle CMS for Static Site Generators,” and I’ll to attest to that. Pages CMS has largely been developed by a single developer, Ronan Berder, but is open source, and accepting pull requests over on GitHub.
Taking a lot of the “good parts” found in other CMS tools, and a single configuration file, Pages CMS combines things into a sleek user interface.
Pages CMS includes lots of options for customization, you can upload media, make editable files, and create entire collections of content. Also, content can have all sorts of different fields, check the docs for the full list of supported types, as well as completely custom fields.
There isn’t really a “back end” to worry about, as content is stored as flat files inside your git repository. Pages CMS provides folks the ability to manage the content within the repo, without needing to actually know how to use Git, and I think that’s neat.
User Authentication works two ways: contributors can log in using GitHub accounts, or contributors can be invited by email, where they’ll receive a password-less, “magic-link,” login URL. This is nice, as GitHub accounts are less common outside of the dev world, shocking, I know.
Oh, and Pages CMS has a very cheap barrier for entry, as it’s free to use.
Pages CMS and Astro content collections
I’ve created a repository on GitHub with Astro and Pages CMS using Astro’s default blog starter, and made it available publicly, so feel free to clone and follow along.
I’ve been a fan of Astro for a while, and Pages CMS works well alongside Astro’s content collection feature. Content collections make globs of data easily available throughout Astro, so you can hydrate content inside Astro pages. These globs of data can be from different sources, such as third-party APIs, but commonly as directories of Markdown files. Guess what Pages CMS is really good at? Managing directories of Markdown files!
Content collections are set up by a collections configuration file. Check out the src/content.config.ts
file in the project, here we are defining a content collection named blog
:
import { glob } from 'astro/loaders';
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
// Load Markdown in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.md' }),
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
});
export const collections = { blog };
The blog
content collection checks the /src/content/blog
directory for files matching the **/*.md
file type, the Markdown file format. The schema
property is optional, however, Astro provides helpful type-checking functionality with Zod, ensuring data saved by Pages CMS works as expected in your Astro site.
Pages CMS Configuration
Alright, now that Astro knows where to look for blog
content, let’s take a look at the Pages CMS configuration file, .pages.config.yml
:
content:
- name: blog
label: Blog
path: src/content/blog
filename: '{year}-{month}-{day}-{fields.title}.md'
type: collection
view:
fields: [heroImage, title, pubDate]
fields:
- name: title
label: Title
type: string
- name: description
label: Description
type: text
- name: pubDate
label: Publication Date
type: date
options:
format: MM/dd/yyyy
- name: updatedDate
label: Last Updated Date
type: date
options:
format: MM/dd/yyyy
- name: heroImage
label: Hero Image
type: image
- name: body
label: Body
type: rich-text
- name: site-settings
label: Site Settings
path: src/config/site.json
type: file
fields:
- name: title
label: Website title
type: string
- name: description
label: Website description
type: string
description: Will be used for any page with no description.
- name: url
label: Website URL
type: string
pattern: ^(https?://)?(www.)?[a-zA-Z0-9.-]+.[a-zA-Z]{2,}(/[^s]*)?$
- name: cover
label: Preview image
type: image
description: Image used in the social preview on social networks (e.g. Facebook, Twitter...)
media:
input: public/media
output: /media
There is a lot going on in there, but inside the content
section, let’s zoom in on the blog
object.
- name: blog
label: Blog
path: src/content/blog
filename: '{year}-{month}-{day}-{fields.title}.md'
type: collection
view:
fields: [heroImage, title, pubDate]
fields:
- name: title
label: Title
type: string
- name: description
label: Description
type: text
- name: pubDate
label: Publication Date
type: date
options:
format: MM/dd/yyyy
- name: updatedDate
label: Last Updated Date
type: date
options:
format: MM/dd/yyyy
- name: heroImage
label: Hero Image
type: image
- name: body
label: Body
type: rich-text
We can point Pages CMS to the directory we want to save Markdown files using the path
property, matching it up to the /src/content/blog/
location Astro looks for content.
path: src/content/blog
For the filename
we can provide a pattern template to use when Pages CMS saves the file to the content collection directory. In this case, it’s using the file date’s year
, month
, and day
, as well as the blog item’s title, by using fields.title
to reference the title field. The filename can be customized in many different ways, to fit your scenario.
filename: '{year}-{month}-{day}-{fields.title}.md'
The type
property tells Pages CMS that this is a collection of files, rather than a single editable file (we’ll get to that in a moment).
type: collection
In our Astro content collection configuration, we define our blog
collection with the expectation that the files will contain a few bits of meta data such as: title
, description
, pubDate
, and a few more properties.
We can mirror those requirements in our Pages CMS blog
collection as fields
. Each field can be customized for the type of data you’re looking to collect. Here, I’ve matched these fields up with the default Markdown frontmatter found in the Astro blog starter.
fields:
- name: title
label: Title
type: string
- name: description
label: Description
type: text
- name: pubDate
label: Publication Date
type: date
options:
format: MM/dd/yyyy
- name: updatedDate
label: Last Updated Date
type: date
options:
format: MM/dd/yyyy
- name: heroImage
label: Hero Image
type: image
- name: body
label: Body
type: rich-text
Now, every time we create a new blog
item in Pages CMS, we’ll be able to fill out each of these fields, matching the expected schema for Astro.
Aside from collections of content, Pages CMS also lets you manage editable files, which is useful for a variety of things: site wide variables, feature flags, or even editable navigations.
Take a look at the site-settings
object, here we are setting the type
as file
, and the path
includes the filename site.json
.
- name: site-settings
label: Site Settings
path: src/config/site.json
type: file
fields:
- name: title
label: Website title
type: string
- name: description
label: Website description
type: string
description: Will be used for any page with no description.
- name: url
label: Website URL
type: string
pattern: ^(https?://)?(www.)?[a-zA-Z0-9.-]+.[a-zA-Z]{2,}(/[^s]*)?$
- name: cover
label: Preview image
type: image
description: Image used in the social preview on social networks (e.g. Facebook, Twitter...)
The fields I’ve included are common site-wide settings, such as the site’s title
, description
, url
, and cover
image.
Speaking of images, we can tell Pages CMS where to store media such as images and video.
media:
input: public/media
output: /media
The input
property explains where to store the files, in the /public/media
directory within our project.
The output
property is a helpful little feature that conveniently replaces the file path, specifically for tools that might require specific configuration. For example, Astro uses Vite under the hood, and Vite already knows about the public
directory and complains if it’s included within file paths. Instead, we can set the output
property so Pages CMS will only point image path locations starting at the inner /media
directory instead.
To see what I mean, check out the test post in the src/content/blog/
folder:
---
title: 'Test Post'
description: 'Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.'
pubDate: 05/03/2025
heroImage: '/media/blog-placeholder-1.jpg'
---
The heroImage
now property properly points to /media/...
instead of /public/media/...
.
As far as configurations are concerned, Pages CMS can be as simple or as complex as necessary. You can add as many collections or editable files as needed, as well as customize the fields for each type of content. This gives you a lot of flexibility to create sites!
Connecting to Pages CMS
Now that we have our Astro site set up, and a .pages.config.yml
file, we can connect our site to the Pages CMS online app. As the developer who controls the repository, browse to https://app.pagescms.org/
and sign in using your GitHub account.
You should be presented with some questions about permissions, you may need to choose between giving access to all repositories or specific ones. Personally, I chose to only give access to a single repository, which in this case is my astro-pages-cms-template
repo.
After providing access to the repo, head on back to the Pages CMS application, where you’ll see your project listed under the “Open a Project” headline.
Clicking the open link will take you into the website’s dashboard, where we’ll be able to make updates to our site.
Creating content
Taking a look at our site’s dashboard, we’ll see a navigation on the left side, with some familiar things.
- Blog is the collection we set up inside the
.pages.config.yml
file, this will be where we we can add new entries to the blog. - Site Settings is the editable file we are using to make changes to site-wide variables.
- Media is where our images and other content will live.
- Settings is a spot where we’ll be able to edit our
.pages.config.yml
file directly. - Collaborators allows us to invite other folks to contribute content to the site.
We can create a new blog post by clicking the Add Entry button in the top right
Here we can fill out all the fields for our blog content, then hit the Save button.
After saving, Pages CMS will create the Markdown file, store the file in the proper directory, and automatically commit the changes to our repository. This is how Pages CMS helps us manage our content without needing to use git directly.
Automatically deploying
The only thing left to do is set up automated deployments through the service provider of your choice. Astro has integrations with providers like Netlify, Cloudflare Pages, and Vercel, but can be hosted anywhere you can run node
applications.
Astro is typically very fast to build (thanks to Vite), so while site updates won’t be instant, they will still be fairly quick to deploy. If your site is set up to use Astro’s server-side rendering capabilities, rather than a completely static site, the changes might be much faster to deploy.
Wrapping up
Using a template as reference, we checked out how Astro content collections work alongside Pages CMS. We also learned how to connect our project repository to the Pages CMS app, and how to make content updates through the dashboard. Finally, if you are able, don’t forget to set up an automated deployment, so content publishes quickly.