This solution no longer work using Vercel (formerly Zeit), they have since disabled the ability to deploy a single codebase and unfortunately there is no backward compatibility. I'm currently using Heroku pipelines to achieve this instead now. Vercel has said they will support this in the future, however, there has not been any announcements as to when.
One thing I love about agency work is the different types of problems you get the opportunity to solve. When I founded Cause of a Kind with my business partner, one of our values was to never fall into the trap of a one size fits all solution that so many other agencies fall into. There were already enough agencies out there that no matter what you say to them were going to come back to you with a canned wordpress or drupal or shopify solution. One such case recently came to us from a company that had a number of digital properties they were struggling to manage and keep in sync.
They had five different building sites and a parent company site all running six separate instances of wordpress. It was a huge time suck for the team to manage as they had to login to five different admin portals to update content, and if they wanted to share assets across properties they had to either copy it to the other instance or upload the images individually each time.
Note: This is an opportunity to feel for the poor intern that actually has to do this management and is probably not in the room at the time of the pitch.
They primarily wanted to rebrand all of these sites to use a unified template and look and feel. The other agencies in the pitch were looking to of course run six different wordpress instances just as they had and copy a single template across all of them. They would then have a maintenance contract in place to deal with when they wanted to change global elements like a navigation or add a form field, etc. This is where hidden costs jump in, maintenance costs need to scale to account for more buildings and figure in the cost of a six site context switch. If we could find a way to make the flow of updating the code a one step operation as well as make the internal maintainers life easier we save time and money for everyone involved.
This is where contentful, next.js, and now.sh come in to save the day.
The first problem to solve is sharing and managing content across all of the properties. Using Contentful was a no-brainer for us, and in reality any headless CMS like Sanity or Prismic could accomplish this, but we are most familiar with the Contentful product. Here we could create a content infrastructure that would facilitate the publishing of Content on one or many of the properties.
Note: Your SEO team is probably not going to want to share the same text across properties, but imagery and graphics are definitely something you don't want to try to come up with six unique versions for every time. Also the content team can easily create duplicates of existing copy and update it to be unique all in one interface.
I'm not going to go into the specifics of how to configure Contentful because this depends largely on the information architecture of the existing site. At a high level we have page containers where the content manager can select the property for that page to be published on and that dynamically updates the preview button so that it will direct them to the right preview site, this is all baked into Contentful quite easily.
Ok, now the tricky bit, how do we deploy six preview environments and six live environments with a single command. So a total of twelve applications, one for preview/staging and another for the production site. Here we are going to use now.sh and a little documented feature that allows you to have multiple config files in a single code base.
Let's start with the parent site:
At the root of our next.js codebase add two files now.json
and preview.now.json
.
Our now.json
file looks like this:
{
"version": 2,
"name": "parent_site",
"build": {
"env": {
"CONTENTFUL_SPACE_ID": "@contentful_space_id",
"CONTENTFUL_ACCESS_TOKEN": "@contentful_access_token",
"CONTENTFUL_HOST": "cdn.contentful.com",
"CONTENTFUL_PREVIEW": "false"
}
},
"builds": [{ "src": "next.config.js", "use": "@now/next" }],
"routes": [
{ "src": "/", "dest": "/index" }
]
}
Note: the environment variables with the @
sign are now.sh secret variables that are set in the command line. You will want to use these for the Contentful space id and access tokens so they are not checked into version control.
The command to add these is: now secrets add
This is the default now.json
file, if you decide to use the github integration witn Zeit, this is the file that will get run automatically. At the time of this writing Now.sh DOES NOT support automatically deploying from multiple config files from git.
Next we are going to make a new file called preview.now.json
:
{
"version": 2,
"name": "preview_parent_site",
"build": {
"env": {
"CONTENTFUL_SPACE_ID": "@contentful_space_id",
"CONTENTFUL_PREVIEW_ACCESS_TOKEN": "@contentful_preview_access_token",
"CTF_PREVIEW_HOST": "preview.contentful.com",
"CTF_PREVIEW": "true"
}
},
"builds": [{ "src": "next.config.js", "use": "@now/next" }],
"routes": [
{ "src": "/", "dest": "/index" }
]
}
Here we made a few changes. We used the Contentful's preview access token as well as the preview host to enable previewing of unpublished content. If you only had a single site to support we can stop here. We now have a production and a preview environment that can be deployed from the command line.
Inside of our package.json
file we can setup some deployment scripts:
"scripts": {
"dev": "now dev",
"build": "next build",
"start": "next start",
"deploy:preview": "now -A preview.now.json && now -A preview.now.json --prod",
"deploy:prod": "now && now --prod"
}
This allow us to run yarn run deploy:preview
or yarn run deploy:prod
to deploy our preview environment then our production environment. Typically if I add some features to a site on a branch I will deploy them to the preview branch first so the client can review before deploying to production.
Hopefully by now you can see where this is going. To add more sites to this setup you just have to create the requisite configuration files.
siteoneprod.now.json
siteonepreview.now.json
sitetwoprod.now.json
sitethreepreview.now.json
... and so on and so forth
We can then add individualized deployment scripts for each site.
`"scripts": {
...
"deploy:siteone:preview": "now -A siteonepreview.now.json && now -A siteonepreview.now.json --prod",
"deploy:siteone:prod": "now -A siteoneprod.now.json && now -A siteoneprod.now.json --prod",
"deploy:sitetwo:preview": "now -A sitetwopreview.now.json && now -A sitetwopreview.now.json --prod",
"deploy:sitetwo:prod": "now -A sitetwoprod.now.json && now -A sitetwoprod.now.json --prod",
"deploy:preview": "yarn run deploy:siteone:preview && yarn run deploy:sitetwo:preview",
"deploy:prod": "yarn run deploy:siteone:prod && yarn run deploy:sitetwo:prod"
}`
These are now all set to be hooked up to different domains however you would like to set them up. Things that I would love to see added to Zeit's product here would be full support for running multiple config files when you merge into master or create a new branch. This would allow auto deployment of the preview environments when we are staging changes to all sites as well as automatic deployment pipelines off of master. Right now, the benefits of being able to deploy this many sites, this easily, at the now.sh pricing structure, to this platform outweighs the disadvantages.
It took quite a bit of research for me to figure out this pipeline so I'm hoping that this saves someone looking to do the same thing some time and energy.