Cosmic JS Blog Stay tuned for community news, company announcements 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

Adding SSL to your website couldn't be easier with Cosmic JS.  Simply go to your bucket > Settings > Deploy Web App > Add SSL to Domains and click the "Add SSL" button (upgrade if necessary).  In a few seconds, all of your domains and subdomains on your deployed application will have the security of SSL from Let's Encrypt.  Plus the certs will automatically renew, so you will never have to update your SSL again!  Here is a short tutorial to show you how easy it is:

Extendible Objects is one of the most powerful features of Cosmic JS.  And this powerful feature just became more intuitive with a new property added to all endpoint responses: Metadata.

We sat down with Abe Hendricks for our latest installment of the Cosmic JS Developer Spotlight Series. 

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.

Brand Managers manage, well, brands.  Seems simple enough right?  It does in title and theory alone.  Managing a brand means managing the brand’s tone, voice, messaging, consumer segmentation, price points, marketing, advertising and all of the subsequent elements and assets that fall out of such an engagement.  Having managed brands for years myself, I can attest to the magnitude of the job at hand.  Once the client has agreed upon a general direction, it becomes the brand manager’s responsibility to ignite passion and results from the internal agency team that services the account.  As my workload would increase month over month as business heated up or an account was grown, I went through the whole Automation | Delegation | Elimination routine to see what I could cut out of my schedule as busy work.  The problem?  I was only one person.  I could only affect hours tallies on my end, but had little to no influence over design, development, copywriting & production. 

The area that always seemed to be the weak link in terms of staying on budget was in development.  Boutique agencies struggle to attract qualified talent with their long work hours and subpar compensation, and then after taking a gamble on a more junior talent that is developed over time, retention is a beating.  Companies line up with development jobs that pay 2X, sometimes 3X what a boutique agency is willing to part with for that developer’s compensation package.  This is all before taking into account staging servers, hosting servers, CMS logins, local installations of CMS systems and all of the red tape and bureaucracy associated that can bog down a productive workflow.  As a result, my quotes back to clients for websites, microsite, landing pages and applications were always a bit higher than they were expecting.  I started searching for a cloud-based solution to my CMS woes to cut out some of the middle men and see if there was an easier way to 'get this digital property live'. 

I found 
Cosmic JS.  Had I been told as a Brand Manager that I could eliminate the local CMS, the hosting server and the shared logins of content editing, I would have seen the value immediately.  No longer having to build APIs on a per-CMS / per-client basis, no longer having to build out a proprietary backend, yet still attaining the same custom-value would have been a lifesaver and a half for a brand manager focused on the bottom line.  It would have easily cut my back end developers’ hours estimates by 40%, eliminated costly hosting servers and would have streamlined content-centric employees within the agency to not have to deal with the red tape of updating content within a traditional CMS. 

Whether we picked out a
content-ready application or plugged GitHub into Cosmic JS, I’m seeing time and cost savings at every turn.  Music to a brand manager’s ears, and music to a boutique agency’s margin and bottom line.  As it turns out, it also benefits the client as their content is put first, their content is pushed live more quickly and is devourable globally on any device.

A new Developer Hero joins us in our latest installment of the Cosmic JS Developer Spotlight Series. We sat down with Brian Mullis, a developer in Portland who  lead the charge on innovative app development for his interactive agency.