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



Update

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.

TL;DR

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!')})app.post('/rebuild-site', (req, res) => {  buildSite()  res.end('Site rebuilt!')})app.get('*', (req, res) => {  res.redirect('/404')})app.listen(app.get('port') || 3000, () => {  console.info('==> 🌎  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 = args.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/index.md that will be rendered to HTML to /build/docs/index.html at the route /docs.  Here's the markdown in src/docs/index.md:


---
title: Documentation
layout: markdown.html
---
This documentation page is powered by Markdown.
Here's a block of code:
```bash
$ cd awesome
$ yarn
```
Here's another block of code:
```javascript
var fs = require('fs')
var async = require('async')
var mkdirp = require('mkdirp')
var Handlebars = require('handlebars')
module.exports = (args, done) => {
  var page = args.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()
          })
        })
      }
    }
  ])
}
```


Any new folder that you add to src with an index.md 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 https://github.com/cosmicjs/static-website
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-sitewhenever 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.

Conclusion

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 tutorial I’m going to show you how to build a user-driven photo gallery, powered by Angular JS, hosted on the Cosmic JS App Server.

At the request of the Cosmic JS Community, new Multi-Bucket Pricing Tiers are now Available for teams. Introducing, Cosmic Clusters. 

Extensions are a powerful component of the Cosmic JS that allow you to create custom views in your Bucket Dashboard. We've recently made some updates to make using this feature even better.

Coding Dojo provides curriculum, software, hardware, instructors, class sessions, conference rooms, thought-leadership and networking events as part of its added-value to its students and community. They also partner with organizations that would hire its students upon graduation.

In this short tutorial I'll show you how easy it is to add a CMS to a simple browser app using the Cosmic JS API.   It will literally take you 2 minutes to build.  Our app will consist of just 3 files:

1. index.html
2. app.js
2. package.json

Let's get started shall we?  In your terminal of choice run the following commands:

mkdir easy-browser-example
cd easy-browser-example
npm install cosmicjs
npm install browserify -g

Now let's build our index.html file.  Run the following command in your terminal:

vim index.html

Add the following to our index.html file:

<!DOCTYPE html>
<html>
<head>
  <title>Cosmic JS Easy Browser Example</title>
</head>
<body>
  <h1 id="title">If you see this, something isn't working...</h1>
  <div id="content"></div>
  <div id="metafields"></div>
  <script src="app.browser.js"></script>
</body>
</html>

We could just as easily use jQuery and Ajax to render our content, but for this example we will use the official Cosmic JS Node.js package. Now create a file called app.js:

vim app.js

And add the following to app.js:

// app.js
var Cosmic = require('cosmicjs')
const bucket = { slug: 'easy-browser-example' }
const object = { slug: 'home' }
Cosmic.getObject({ bucket }, object, (err, res) => { var object = res.object
  document.getElementById('title').innerHTML = object.title
  document.getElementById('content').innerHTML = object.content
  document.getElementById('metafields').innerHTML = '<pre>' + JSON.stringify(object.metafields, null, 2) + '</pre>'
})

Notice that in our app.js file we are returning content from the Cosmic JS API, and then attaching our content to the DOM elements at "title","content" and "metafields".

Next we'll add a package.json file that will allow us to add some simple scripts to "browserify" our app.js file:

vim package.json

Add the following to a new file titled package.json:

{
  "name": "easy-browser-example",
  "main": "app.js",
  "scripts": {
    "browserify": "browserify app.js -o app.browser.js"
  }
}

Now let's run our our scripts to bundle our code into the browser.  Run the following script which will bundle the new file to app.browser.js:

npm run browserify

Now view the index.html file in your browser and you will see that the content from our example bucket "easy-browser-example" can be seen and the metafields data is rendered to a string to show you what data is available.  Taking this a step further, you can see how powerful this can be if you add React or Angular into the mix.

I hope you enjoyed this short tutorial.  If you have not already, you can sign up for a Cosmic JS account, and begin playing with this example using content from your own bucket.  Thanks and happy building!


We have revised the pricing for all Bucket and Cluster plans. Take a look at the pricing page to see the new, simplified pricing including a new Community Plan that gives you a Free Personal Bucket forever.