Building a Portal with Contentful and Next.js on now.sh

Julian Rubisch
Boiling Plates
Published in
9 min readMay 11, 2018

I admit, I have done websites. The WordPress way. Clients expecting a — decent, but nonetheless — blogging framework to behave like a customizable online publication, media storage/streaming service and/or fully-featured CRM. Over the time it became so frustrating to patch and twist WordPress with plugins to somehow enable that one desired functionality that I’ve avoided doing websites as much as I could. Plus, the always looming security flaws, backup and restore concerns, tedious migrations etc.

I was not aware of, or at least didn’t consider, the fact that there are headless, cloud-first CMS’s like Contentful out there. When I first learned about it, I hesitated because of the rather ambitious pricing model. 39$ for a rather small set-up, that sounded adventurous. But the more I read about the benefits, such as

  • fully customizing your content model, from defining associations between types down to performing validations on single fields,
  • ready-to-go development kits for almost every relevant platform,
  • state-of-the-art caching,
  • easy to use webhooks,
  • etc…

the platform intrigued me more and more. After all, you could incorporate your whole site into a static website, scaling down hosting costs to the very minimum possible. You could as well build multiple front-ends, manage entries and assets via a CLI, and so on.

What about my WordPress templates?

Scratch them. True, there are a ton of them to choose from out there. But very few of them actually work well on mobile devices, let alone allow customization in a way that will satisfy every need. There will always be a major trade-off in such an environment.

Now I’m not saying that this setup is for everyone. If your local grocery store comes asking for a website, you’d probably sell him a WordPress site (if you’re that type of person), or, more ethically correct, recommend him to just go with a Facebook page.

But the type of projects/web frontends I’m occupied with mostly at the moment have a content model that is so far away from the usual pages/categories/tags/posts approach that I embraced Contentful and have been very happy with it since then. And with modern HTML frontend frameworks like Bootstrap, it’s just as easy to create your own templates from scratch, and you got full flexibility instead of a few hooks the theme designer allows for you.

Let’s get our hands dirty.

Disclaimer: The rest of the article is even more opinionated than the start :)

So I’ve pondered what kind of set-up would make sense and in the end came up with an isomorphic javascript one. When I say that, I typically provoke one of two types of reaction:

  • scoffing, or
  • cheering.

To be honest, I most of the time am rather indifferent towards technology. I weigh the pros against the cons and choose what suits my business case best. True, that way I seldom reach true virtuosity, but the inverse is also dangerous: If all you know is a hammer, everything starts looking like a nail.

Since I had some prior React programming experience (and a now.sh account), I chose next.js as a framework. What I generally like about Zeit’s projects is their minimalistic approach to everything, and it is the same with next.js. Compared to create-react-app the use case is pretty slim: Server-rendered React-powered sites. I wouldn't even go so far and say applications are their primary target. No need for routers, state containers out of the box, you build your site navigation with <Link> tags and have that magical /pages directory that will be mounted on the root of your app. Every React component you put in there will correspond to a route, so /pages/event.js will be accessible via /event/, etc.

There is also a /static folder that will hold your assets, but that's about it. Very opinionated, but you get

  • automatic code splitting,
  • additional lifecycle methods for server-side data fetching
  • page prefetching
  • dynamic imports
  • and more…

Again, the tight scaffold that next puts on your app will only fit a tiny percentage of use cases. But for this one, and for me, it was an almost perfect fit. And partially, I have to admit, that is due to the fact that with next, there is no need to separate client from server side code. Less possible breaking points, less boilerplate code, more development speed that means for me.

Pulling in the Boiling Plate

After a successful project launch like this, I sometimes take a step back and reduce everything to a boilerplate (hence the publication title). I also write things up so I don’t forget them. And as a few colleagues have pointed out to me, others may find this useful, too. So yes, this is yet another TIL blog… please excuse my mediocrity :)

Basic next.js

So let’s dissect this one piece at a time. We start with our dependencies, and review our package.json. The relevant parts for the next.js setup are:

{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
...
"dependencies": {
"express": "^4.16.3",
...
"next": "^5.1.0",
"react": "^16.3.1",
"react-dom": "^16.3.1",
}
}

We’ll get to investigating why express is needed here later, but the main three packages are next, react, and react-dom, that's it. Let's just add an index page for now, and put it in /pages:

const Index = () => (
<div style={{width: 640, maxWidth: '100%', margin: 'auto'}}>
<h1>Hello from Next</h1>
<img src="static/boiling_plates.svg" />
</div>
);

export default Index;

Let’s install the packages, hit npm run dev and see what we get (note: I'm using node v8.9.4 for this walkthrough). Navigate to http://localhost:3000 and you should get:

Initial index.js

Hey! Looks like we’re done. No, seriously. We want a cool, responsive look and feel, right? Let’s bootstrap it.

Bootstrap 4

This needs a little setup. First, some additional entries to our package.json:

{   
...
"dependencies": {
"autoprefixer": "^8.2.0",
"bootstrap": "^4.0.0",
"jquery": "^3.3.1",
"node-sass": "^4.8.3",
"popper.js": "^1.14.3",
"postcss-easy-import": "^3.0.0",
"postcss-loader": "^2.1.3",
"raw-loader": "^0.5.1",
"sass-loader": "^6.0.7"
}
}

Let’s see. jQuery and popper.js are required by Bootstrap, so that’s that. In next.js.config, which is a sort of next-flavoured webpack.config.js, we configure the loading of SCSS files like so:

...
{
test: /\.s(a|c)ss$/,
use: ['raw-loader', 'postcss-loader',
{ loader: 'sass-loader',
options: {
includePaths: ['styles', 'node_modules']
.map((d) => path.join(__dirname, d))
.map((g) => glob.sync(g))
.reduce((a, c) => a.concat(c), [])
}
}
]
}
...

So that explains the use of raw-loader and postcss-loader: SCSS files are loaded and afterwards processed by postcss. So let's look at that config next:

module.exports = {
plugins: [
require('postcss-easy-import')({prefix: '_'}),
require('autoprefixer')({ /* ...options */ })
]
}

(postcss.config.js)

We use it to autoprefix our stylesheets and the imports too (hence the ‘_’ prefix: load imports partial-style).

Afterwards sass-loader will do the the magic on our SCSS and concatenate it.

Now, for the Bootstrap part we also need to push in the ProvidePlugin as per the docs:

config.plugins.push(
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default'],
}),
);

So how do we get the stylesheet into our page? This is what it looks like (index.scss), pretty unexciting :)

@import "~bootstrap/scss/bootstrap";

First, let’s create a folder called /components (can be called anything, really) and create a layout.js component.

It seems like the preferred way to load CSS in Next is to more or less copy-and-paste it into a <Head> section, css-in-js-style:

import stylesheet from '../styles/index.scss';
// ...
<Head>
<title>Next / Contentful / Bootstrap 4 Boilerplate</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<style dangerouslySetInnerHTML={{ __html: stylesheet }} />
</Head>

No minifying here. I’m pretty sure there’s ways to optimize that, but I’ll leave it at that for now.

To complete the Bootstrap setup, we need the javascript as well, yes? This was the trickiest part to figure out. Because of the server-side-rendering, we need to put the initialization into the componentDidMount React lifecycle hook:

componentDidMount() {
window.$ = window.jQuery = require('jquery');
require('bootstrap');
}

That part is only executed when the markup is ready, client side. So let’s add some text and bootstrap utility classes, wrap the Index page in our Layout, and refresh the dev server:

<Layout>
<div className="text-center">
<h1>Hello from Next</h1>
<p className="lead">Lorem ipsum... yeah whatever...</p>
<img className="img-fluid w-75 mt-4" src="static/boiling_plates.svg" />
</div>
</Layout>

This is what we get, Bootstrap seems to work fine:

Making it Contentful

I’m not going to go through the whole Contentful UI and setup procedure here, please refer to the docs :)

I’ll instead guide you into how to set up a Contentful client in this environment. First, npm i --save contentful and make a .env file with your credentials and Space ID:

CTF_SPACE_ID=<your space id>
CTF_CDA_TOKEN=<your CDA token>
CTF_CPA_TOKEN=<your CPA token>

We are going to need the dotenv npm module for that: npm i --save dotenv and load it in next.config.js:

require('dotenv').config();

That will make our environment variables accessible via process.env. Now also load in the EnvironmentPlugin in the same file:

config.plugins.push(
new webpack.EnvironmentPlugin(process.env),
//...
)

That will make those variables available at compile time! Now we can reference them in our code, e.g. in the common/contentful.js file:

const contentful = require('contentful');

const defaultConfig = {
CTF_SPACE_ID: process.env.CTF_SPACE_ID,
CTF_CDA_TOKEN: process.env.CTF_CDA_TOKEN,
CTF_CPA_TOKEN: process.env.CTF_CPA_TOKEN
};

module.exports = {
createClient (config = defaultConfig) {
const options = {
host: 'preview.contentful.com',
space: config.CTF_SPACE_ID,
accessToken: config.CTF_CPA_TOKEN
};

if(process.env.NODE_ENV === 'production' && !process.env.STAGING) {
options.host = 'cdn.contentful.com';
options.accessToken = config.CTF_CDA_TOKEN;
}

return contentful.createClient(options);
}
};

The purpose of that file is to provide a factory method that will configure your Contentful client according to whether you are in the development or production environment, or, in our case, a custom STAGING environment, which I find useful. The switch we make here is as to whether we use the Delivery API (production) or Preview API (development, staging), which will also include unpublished entries.

Let’s now pretend that we are already going to load some Contentful data in our index page. We have to import the factory method we just made up:

import { createClient } from '../common/contentful';

//...

Index.getInitialProps = async () => {
const client = createClient();

const entries = await client.getEntries({
// some query
});

return { someEntryAsProp: entries.items[0] };
};

Later in that class, we call getInitialProps, thats a lifecycle method nextjs adds to our React class. We can use async code here, and simply fetch some entries from Contentful, and pass them via props. In our render method, that'd be available as a prop then:

//...

<Layout>
<div className="text-center">
<h1>Hello from Next</h1>
<p>{someEntryAsProp.fields.text}</p> // <-- HERE
<img className="img-fluid w-75 mt-4" src="static/boiling_plates.svg" />
</div>
</Layout>

//...

And that’s about it. Now we can perform arbitrary queries on the Contentful backend and fetch our content, either on the server side via getInitialProps, or on the client side if we do so in componentDidMount.

Server.js

Now a side note on that Express server.js hiding in our root directory. A detailed explanation of nextjs is also beyond the scope of this article (it's already pretty lengthy :)) - if your interested, look here. It turns out that next is very smart, but not smart enough to guess your route masking. What do I mean by that?

In next, you could declare a link like that:

<Link as={`/a/${props.id}`} href={`/article?id=${props.id}`}>
<a>{props.title}</a>
</Link>

And that would work (it’d give you clean URLs of type /a/my-awesome-article) as long as navigation takes place on the client side, because next does some magic with your browser history (pushState etc.).

However, if you refresh your browser window after navigating to that route, that would lead to a 404. So in the server.js there is this route, a translation of the shortened URL to the correct page in the /pages directory and append the id in a query string.

server.get('/a/:id', (req, res) => {
const actualPage = '/article';
const queryParams = { id: req.params.id };
app.render(req, res, actualPage, queryParams);
});

Deploy it to Now

That part is the easiest of all. Because both next and now are Zeit projects, it should be, no?

Turns out now will deploy anything that has a npm build and a npm start script, basically. The only thing we have to take care of, are our project secrets. In now, that's pretty easy to handle. We declare them in our console:

> now secrets add ctf_space_id "my_value"
> now secrets add ctf_cda_token "my_value"
> now secrets add ctf_cpa_token "my_value"

And reference them in our now.json:

{
"env": {
"CTF_SPACE_ID": "@ctf_space_id",
"CTF_CDA_TOKEN": "@ctf_cda_token",
"CTF_CPA_TOKEN": "@ctf_cpa_token"
}
}

That’s it. Deploy to our staging environment by additionally specifying the STAGING environment variable:

> now -e STAGING=1

and finally to production:

> now

Find the actual boilerplate here.

--

--