export const metadata = {
  title: 'Displaying the right Vercel deployment URLs in Next.js',
  description:
    'A TIL about caching, Git branch management and React Server Components.',
  tags: ['til', 'rsc', 'git', 'next.js', 'turborepo', 'vercel'],
  publicationDate: '2023-09-09'
}

import { url, resolve, filePathToUrlPath, hnDiscussionUrl } from 'lib/paths'

## The issue

At the bottom of this page, I have a <a href={hnDiscussionUrl(resolve(import.meta.url))}>"Discuss on Hacker News"</a>
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 environment
- `DEPLOYMENT_URL=next.francoisbest.com` in the Preview environment tied to the `next` staging branch.

From there I can then compute the correct URL for my deployment:

```ts
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:

<figure>
  ![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.](./ungit.png)
  <figcaption className="text-center">
    I use [Ungit](https://github.com/FredrikNoren/ungit) to display and navigate
    Git histories
  </figcaption>
</figure>

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](/robots.txt)
- [sitemap.xml](/sitemap.xml)
- [RSS feed URLs](/posts/feed/rss.xml)
- [Metadata](https://nextjs.org/docs/app/building-your-application/optimizing/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](https://turbo.build/repo/docs/core-concepts/caching/environment-variable-inputs)
the app needs:

```json {9-15} title="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

<Note status="warning">
  The solutions presented below did <strong>not</strong> work.
</Note>

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:

<figure>
  ![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.](./vercel.png)
  <figcaption className="text-center">
    You can find this setting in your project > Settings > Git
  </figcaption>
</figure>

<Note>

If you are used to non-zero Unix exit codes indicating a problem, this may seem
weird.

However, it's a double negative situation here: saying "no" to an "Ignore build" to actually trigger the build.
It's a bit confusing.

</Note>

On top of that, I tried setting the [`VERCEL_FORCE_NO_BUILD_CACHE`](https://vercel.com/docs/deployments/troubleshoot-a-build#managing-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.

<Note status="info">
  I did that before I realised my problem was with the Turbo cache.

It can be good to know that disabling the build cache doesn't affect Turbo.

</Note>
