Displaying the right Vercel deployment URLs in Next.js
The issue
At the bottom of this page, I have a “Discuss on Hacker News” link. It points to the Hacker News search engine with the URL of the current page.
I was having issues on Vercel when deploying first on a preview branch, then
merging my main
production branch onto it: production links would show the preview URL.
For example, instead of https://francoisbest.com/whatever, I ended up with links to https://next.francoisbest.com/whatever.
Using custom domains on Vercel
When deploying to a custom domain, the VERCEL_URL
environment variable is
still set to the generated *.vercel.app
URL.
To pass the domain which my deployment is tied to, I added the following environment variables:
DEPLOYMENT_URL=francoisbest.com
in the Production environmentDEPLOYMENT_URL=next.francoisbest.com
in the Preview environment tied to thenext
staging branch.
From there I can then compute the correct URL for my deployment:
export function url(routePath: string) {
const base = process.env.DEPLOYMENT_URL ?? process.env.VERCEL_URL
if (base) {
return `https://${base}${routePath}`
}
return `http://localhost:${process.env.PORT ?? 3000}` + routePath
}
This gives me the following:
Branch | URL |
---|---|
main | https://francoisbest.com |
next | https://next.francoisbest.com |
feat/foo | https://{project}-{hash}-{scope}.vercel.app |
Linear Git branching
My branching strategy for this website doesn’t use Pull Requests, but rather sees Git branches as labels that can move up the commit tree, using fast-forward merges:
I use Ungit to display and navigate Git histories
To help with performance, Vercel only considers the Git SHA to trigger a new build. Any branch pushed to the same SHA will reuse the build cache to speed up deployments.
The problem is that this will cause the preview URLs to show up on the production deployment. I use React Server Components to render those URLs, that render to HTML only once.
A solution would be to render this link as a client component,
and tie it to the NEXT_PUBLIC_VERCEL_URL
— which is correct at runtime —
instead of burning in the build-only VERCEL_URL
in a server component.
However, the correct URL is also needed in a couple of places where this solution won’t work:
- robots.txt
- sitemap.xml
- RSS feed URLs
- Metadata base URLs
Turborepo
This repository uses Turborepo, for which Vercel has a Remote Build Cache enabled by default.
If Turbo was to replay the cache entry, the HTML generated by the server components would be incorrect. So it needs to be told of the environment variables the app needs:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"dev": {
"cache": false
},
"francoisbest.com#build": {
"outputs": [".next/**", "!.next/cache/**"],
"env": [
"DEPLOYMENT_URL",
"VERCEL_ENV",
"VERCEL_GIT_COMMIT_REF",
"VERCEL_GIT_COMMIT_SHA",
"VERCEL_URL"
]
},
"lint": {}
}
}
This essentially opts out of Turbo caching, since the generated VERCEL_URL is different for each deployment.
I could also set the TURBO_FORCE
environment variable to true
to opt-out of
Turbo caching entirely, but it prevents other packages in the monorepo from
taking advantage of the Turbo cache.
Other failed attempts at a solution
I first tried to tell Vercel to rebuild for each push by setting the “Ignored Build Step”
behavior to “Custom” with a value of exit 1
, but it did not work:
You can find this setting in your project > Settings > Git
On top of that, I tried setting the VERCEL_FORCE_NO_BUILD_CACHE
environment variable to 1
for deployments.
While it does show in the logs that the build cache step is skipped, this repository uses Turborepo and the build logs show that Turbo gets a cache hit.
Freelance developer & founder