Tony Spiro
October 25, 2016
Update
Check out the Jamstack CMS knowledge base page to learn how you can use Cosmic to build a static websites site using Gatsby, Nuxt.js, Gridsome, and more.
Continue reading for an example using Metalsmith.
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 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.
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 comes in.
API-Powered, Static Website
In building our Cosmic-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 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 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 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 metalsmithPrism = require('metalsmith-prism'); 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( { langPrefix: 'language-' } )) .use(metalsmithPrism()) .use(permalinks()) .use(layouts({ engine: 'handlebars', partials: 'layouts/partials' })) .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 API is called to get the Pages from our Cosmic 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 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([ // Register partials callback => { fs.readFile(__dirname + '/layouts/partials/header.html', 'utf8', (err, data) => { if (err) { return console.log(err) } Handlebars.registerPartial('header', data) callback() }) }, callback => { fs.readFile(__dirname + '/layouts/partials/footer.html', 'utf8', (err, data) => { if (err) { return console.log(err) } Handlebars.registerPartial('footer', data) callback() }) }, 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 title = page.title var markup = locals.template({ page, pages, cosmic, year, title }) // 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 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 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 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 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, 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 Slack channel, or reach out to us on Twitter.