Cosmic JS Blog Stay tuned for feature roll-outs, community news, and updates from the Cosmic JS team.

How to Build an API-Powered, Static Website: The Best of Both Worlds


This is an older post that shows you how to build a static website using Metalsmith. There are also Cosmic JS resources for static website development using React, Vue, and Gatsby. Go to the Quickstart Guide for instructions on how to install these starters.

Static websites have been around for awhile, like since the beginning of the internet (the web used to be all static websites). Now we’re seeing a trend that is bringing these HTML-centric websites back, why?  Because HTML renders really fast when you don’t have a database or a server-side language slowing things down.  That’s not to say that programming languages should be done away with, because pretty much every major website you visit has some sort of server-side language and database attached to it.  But for smaller websites that don’t involve things like user session management or time-sensitive dynamic data, a static site will do, and actually may be more ideal.

In this article I’m going to show you how to build a static website that gives you the best of both worlds:  A website that is both dynamically powered by the Cosmic JS API and also a static website that can be edited using Markdown files.  As a bonus, you can also setup automatic builds using Webhooks.


View the Static Website demo here.

View the Static Website codebase on GitHub here.

View the Static Website App page on Cosmic JS.

The Problem With Markdown

A lot of static website processes involve translating Markdown to HTML.  And developers love Markdown, for good reason:  Markdown allows developers to save time by using a shorthand language that translates into HTML.

But most non-technical people would rather not have to learn Markdown to edit content.  Most content editors would rather use an intuitive content management system.  That’s where Cosmic JS comes in.

API-Powered, Static Website

In building our Cosmic JS-powered static website, our goal is to make it easy for developers to build the website and easy for non-developers to edit and deploy content.  For editors, adding new content will be as easy as going into the Cosmic JS Dashboard, adding a new Page, editing the different content areas and clicking Save.  For developers, they will still have the benefit of using Markdown.  By building our website this way, we can get the whole team involved.  Our static website will have the best of both worlds: A fast, static website that is easily managed by a developer using Markdown files, or content editor using the Cosmic JS CMS API.

Getting Started

To build our API-powered static website, we will need a few things:

1. A build process that converts the API response into static pages.

2. A rebuild process that makes it easy to rebuild our app after content edits.

3. The ability to create pages using Markdown.

To follow along with the steps download the Static Website on GitHub.

The Build Process

To handle our static website's files and rebuild process we’ll use Node.js, specifically the Express framework for our light-weight server.  In our app.js file we have the following:

var buildSite = require('./build-site')buildSite()var express = require('express')var app = express()app.set('port', process.env.PORT || 3000)app.use(express.static('build'))app.get('/rebuild-site', (req, res) => {  buildSite()  res.end('Site rebuilt!')})'/rebuild-site', (req, res) => {  buildSite()  res.end('Site rebuilt!')})app.get('*', (req, res) => {  res.redirect('/404')})app.listen(app.get('port') || 3000, () => {'==> 🌎  Go to http://localhost:%s', app.get('port'))})

There’s a few things happening.  First we’re initiating the build process right on app startup.  Our static files will be served from the /build folder which will also be the root of our application.

Other Routes

GET /rebuild-site will rebuild the website.

POST /rebuild-site is used for our Cosmic JS Webhooks.

GET /* When a route is not found and "falls through" our designated routes, it will redirect to the 404 page located at /404.

Building HTML Pages from the API

Next let’s look at the build process.  Here's what's in the build-site.js file:

var Metalsmith = require('metalsmith')var markdown = require('metalsmith-markdown')var layouts = require('metalsmith-layouts')var permalinks = require('metalsmith-permalinks')var sass = require('metalsmith-sass')var Cosmic = require('cosmicjs')var async = require('async')var mkdirp = require('mkdirp')var del = require('del')var mv = require('mv')var createPage = require('./create-page')var config = require('./config')module.exports = () => {  async.series([    // Create build-new folder    callback => {      mkdirp(__dirname + '/build-new', err => {        callback()      })    },    callback => {      Cosmic.getObjects(config.cosmicjs, (err, res) => {        var pages = res.objects.type.pages        var cosmic = res        // Create dynamic static pages        async.eachSeries(pages, (page, callbackEach) => {          var args = {            page: page,            pages: pages,            cosmic: cosmic          }          createPage(args, callbackEach)        }, () => {          // Create markdown static pages          var year = (new Date()).getFullYear() // make your footer year dynamic ;)           Metalsmith(__dirname)            .metadata({              cosmic: cosmic,              year: year            })                   .source('./src')            .destination('./build-new')            .clean(false)            .use(sass({              outputDir: 'css/',              sourceMap: true,              sourceMapContents: true            }))            .use(markdown())            .use(permalinks())            .use(layouts({              engine: 'handlebars'            }))            .build((err, files) => {              if (err) { throw err }              callback()            })        })      })    },    // Delete build folder    callback => {      del([__dirname + '/build']).then(() => {        callback()      })    },    // Move build-new to build folder    callback => {      mv(__dirname + '/build-new', __dirname + '/build', { mkdirp: true }, () => {        callback()      })    },    // Delete build-new folder    callback => {      del([__dirname + '/build-new']).then(() => {        // done      })    }  ])}

We're using the async module to make sure everything is happening in the correct order.  Here's what's happening:

1. The new build folder is created.
2. The Cosmic JS API is called to get the Pages from our Cosmic JS bucket.
3. The createPage function is called next (we’ll get to this in a moment).
4. Finally Metalsmith takes care of our frontend: preprocessing SASS files into CSS or concatenating and minifying JavaScript, etc.  Along with these frontend processes, Metalsmith will also convert any Markdown files that we have in our src folder into HTML pages in our build folder.

After all static HTML files from the API and from Markdown are added to the build-new folder our build-new folder is renamed to build for a zero-downtime update to our static website.

With Cosmic JS serving content from the CMS API and Metalsmith powering the frontend and Markdown files, you have the ability to let a non-dev manage content in the static site as well as giving the developer the freedom to still use Markdown.

Next let’s look at how the pages are rendered from the API to HTML pages.  In create-page.js we have the following:

var fs = require('fs')var async = require('async')var mkdirp = require('mkdirp')var Handlebars = require('handlebars')module.exports = (args, done) => {  var page =  var pages = args.pages  var cosmic = args.cosmic  var locals = {}  async.series([    callback => {      fs.readFile(__dirname + '/layouts/page.html', 'utf8', (err, data) => {        if (err) {          return console.log(err)        }        var template = Handlebars.compile(data)        locals.template = template        callback()      })    },    () => {      // Set variables      var year = (new Date()).getFullYear() // make your footer year dynamic ;)       var markup = locals.template({ page, pages, cosmic, year })      // If Home page found      if (page.slug === 'home') {        fs.writeFile(__dirname + '/build-new/index.html', markup, err => {          if(err) {            return console.log(err)          }          done()        })      } else {        mkdirp(__dirname + '/build-new/' + page.slug, err => {          fs.writeFile(__dirname + '/build-new/' + page.slug + '/index.html', markup, err => {            if(err) {              return console.log(err)            }            done()          })        })      }    }  ])}

Notice we are reading the contents of page.html from our layouts folder and creating a template using Handlebars.  You can use other template languages, but I prefer Mustache, or Handlebars for its logic-less simplicity.  Next we are passing in our variables from the API to translate the template variables into rendered output.  Next a file write happens to /build/:page.slug:/index.html for all pages in our Cosmic JS bucket.  And a special page is built for our home page at /build/index.html.

As you can see this is a pretty simple process of taking the output of our Cosmic JS bucket and translating it into HTML using Handlebars and the Node.js fs.writeFile function.

Building Pages from Markdown

In our src folder there is a file docs/ that will be rendered to HTML to /build/docs/index.html at the route /docs.  Here's the markdown in src/docs/

title: Documentation
layout: markdown.html
This documentation page is powered by Markdown.
Here's a block of code:
$ cd awesome
$ yarn
Here's another block of code:
var fs = require('fs')
var async = require('async')
var mkdirp = require('mkdirp')
var Handlebars = require('handlebars')
module.exports = (args, done) => {
  var page =
  var pages = args.pages
  var cosmic = args.cosmic
  var locals = {}
    callback => {
      fs.readFile(__dirname + '/layouts/page.html', 'utf8', (err, data) => {
        if (err) {
          return console.log(err)
        var template = Handlebars.compile(data)
        locals.template = template
    () => {
      // Set variables
      var year = (new Date()).getFullYear() // make your footer year dynamic ;) 
      var markup = locals.template({ page, pages, cosmic, year })
      // If Home page found
      if (page.slug === 'home') {
        fs.writeFile(__dirname + '/build-new/index.html', markup, err => {
          if(err) {
            return console.log(err)
      } else {
        mkdirp(__dirname + '/build-new/' + page.slug, err => {
          fs.writeFile(__dirname + '/build-new/' + page.slug + '/index.html', markup, err => {
            if(err) {
              return console.log(err)

Any new folder that you add to src with an Markdown file will create a new route at /your-new-folder.

Starting Our Static Website

If you haven't already, download the GitHub repository for the Static Website by running the following commands:

git clone
cd static-website
yarn install
yarn start

Now go to http://localhost:3000 to see your freshly built website.  Go to http://localhost:3000/rebuild-site whenever you would like to rebuild your website.

Automatic Builds Using Webhooks

Next, to make things even easier for your content editor you can add Webhooks to your Cosmic JS bucket to trigger a rebuild every time content is published in your bucket.

Check out the tutorial to learn how to add Webhooks.


By using Cosmic JS as your CMS API you can have a static website with the best of both worlds: A dynamic website that can be managed using an intuitive content editing experience as well as the ability to add developer-friendly Markdown pages.

I hope you've enjoyed this tutorial on creating an API-powered static website.  If you want to deploy the static website in just a few clicks, sign up for Cosmic JS, create a bucket and look for the Static Website App in the Apps tab of your bucket.  For any questions or help getting started, reach out to us in the Cosmic JS Slack channel, or reach out to us on Twitter.

You may also like

In this Developer Spotlight we sit down with Ben Packer, a web developer who's writing code for a cause.

Did you know that we have some great resources available over on our GitHub page?  Use these packages, examples and clients to help you build your next big project or contribute to an existing one.


1. Cosmic JS Node Package

- This is a great package for your Node.js app.  
Easily add this to your JS setup with npm install cosmicjs

2. Cosmic JS Browser Package

- This is a great package for your your browser-based apps.  
Easily add this to your JS setup with npm install cosmicjs-browser

3. Cosmic JS PHP Client

- This is the official PHP client that makes adding a CMS to your PHP app a breeze.

Example Websites

1. Astral (PHP)

- This is a really cool PHP single page application built using the PHP client on an frontend.

- This is a single page React app that uses React Router and the Flux pattern.  The pages load fast!

I hope you find that these links and resources help you learn more about building with the Cosmic JS content API.  If you have any questions about the API, you can learn more in the Cosmic JS API Documentation.

In an effort to make the data modeling of Metafields even easier, you can now add Metafields at the top, bottom and inline between Metafields.

In this tutorial, we're going to create a small Twitter-like mobile app using React Native. With our app, users will be able to create accounts and log in, see a feed of all of the posts created by themselves and other users, and add their own posts to the feed. The data for all of our users and posts will be managed by Cosmic JS.

The process of building your messenger bot is fairly simple the hardest part is setting up your machine to talk to talk to Facebook. That's why today I'm going to walk you through that real quick. Once it is all done you can get right on the way to creating your own bot.

In the frenzy surrounding new tech, simplicity gets forgotten. Services like Cosmic JS challenge this. So in the spirit of doing the opposite, we're going to build a sleek real estate listing app (with a social flavor) that's as easy to use as a Wordpress blog.