Displaying the right Vercel deployment URLs in Next.js

François Best • 9 September 2023 • 4 min read

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:

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:

BranchURL
mainhttps://francoisbest.com
nexthttps://next.francoisbest.com
feat/foohttps://{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:

Ungit showing a git tree with several commits in line. The "this-is-a-label"
branch can be moved from an old commit to the HEAD with ungit.

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:

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:

turbo.json
{
  "$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:

When a commit is pushed to the Git repository that is connected with your
Project, its SHA will determine if a new Build has to be issued. If the SHA
was deployed before, no new Build will be issued. You can customize this
behavior with a command that exits with code 1 (new Build needed) or code
0.

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.


François Best

Freelance developer & founder