December 1, 2023
A recipe for a staging or preview environment on a static subdomain with the latest changes when using Next.js v14, Vercel, and GitHub Actions. Examples:
When deploying Next.js projects on Vercel, the preview deployment URLs are unique and constantly changing. What motivated this work was integrating with a third party billing provider that needed a stable staging URL.
Recipe overview:
preview
.preview
to latest with a GitHub Action.preview
branch.preview
deployments.robots.txt
for production and previews.With this in place the website will not suffer duplicate content penalty from the preview subdomain which could otherwise negatively effect SEO rankings. Everything is in addition to the regular preview deployments and URLs. Next.js v14 is assumed.
In Vercel it is possible to specify that all deployments on a certain git branch will be hosted on a given subdomain. Follow these steps to add a subdomain for a given branch:
preview
Something to be aware of is that when adding a subdomain for a branch is
that Next.js / Vercel will no longer automatically add the
x-robots-tag: noindex
HTTP header like it does by default for all
preview deployments (all branches except main
). This will be addressed
below in the next.config.json
.
For any of this to work the Next.js project will need to be aware of the
the branch name that is being deployed. When deploying the project with
Vercel, the "Automatically expose System Environment Variables" checkbox
needs to be ticked. It can be found under Project -> Settings ->
Environment Variables. The environment variable we are interested in is
VERCEL_GIT_COMMIT_REF
which is the git branch name that is being deployed.
When the branch name is main
production is assumed, and then a staging
environment is assumed when the branch name is preview
.
Adds a x-robots-tag: noindex
HTTP header to all endpoints when a deployment
is on the preview
branch. By default Vercel adds the same header to all
regular preview deployments but at the time of writing when a subdomain is
specified for a branch it no longer applies. This setting code manually
performs the same task.
const isPreviewBranch = process.env.VERCEL_GIT_COMMIT_REF === "preview";
const defaultHeaders = [];
const headersOnPreview = [
{
source: "/:slug*",
headers: [
{
key: "x-robots-tag",
value: "noindex",
},
],
},
];
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return isPreviewBranch ? headersOnPreview : defaultHeaders;
},
};
module.exports = nextConfig;
// If the branch name is "main"
const isProduction = process.env.VERCEL_GIT_COMMIT_REF === "main";
const production = {
rules: {
userAgent: "*",
allow: "/",
}
};
const preview = {
rules: {
userAgent: "*",
disallow: "/",
}
};
export default function robots() {
return isProduction ? production : preview;
}
This generates the following robots.txt
in production which permits
all search engines indexing on all routes:
User-Agent: *
Allow: /
Conversely on all other preview deployments the contents of robots.txt
will be to respectfully request all bots (search engines) to not index any
routes on the website:
User-Agent: *
Disallow: /
Below is the GitHub Action that automatically updates the preview
branch
to the latest commit that was pushed on any other branch.
The .github/workflows/preview.yml
file:
name: Preview
on:
push:
branches-ignore:
- preview # avoids endless recursion
jobs:
preview:
name: Update preview branch
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Overwrite preview branch
run: |
# Fetch & checkout preview branch,
# reset to pushed commit and force
# overwrite the preview branch
git fetch origin preview
git checkout preview
git reset --hard ${{ github.sha }}
git push -f origin preview
In the current configuration the preview
branch is also updated with
changes pushed to production (main
branch). If that is not desirable
just add "- main
" to the branches-ignore
list.