Review apps served via GitLab artifacts

I have a repo that contains a very small, static website and uses review apps for its merge requests. The relevant bits of the CI config look like this:

stages:
    - build
    - test
    - deploy

build:
    stage: build
    script:
        - # Generate static site...
    artifacts:
        paths:
            - site
    environment:
        name: review/$CI_COMMIT_REF_NAME
        url: "https://$CI_PROJECT_NAMESPACE.gitlab.io/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/public/index.html"
        action: prepare

test:
    script:
        # Do something with the artifact
        # created in the build stage

deploy:review:
    stage: deploy
    script:
        - mv site public
    environment:
        name: review/$CI_COMMIT_REF_NAME
        url: "https://$CI_PROJECT_NAMESPACE.gitlab.io/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/public/index.html"
        auto_stop_in: ...

The value of environment.url is needed in the build stage, so that the site generator can set the canonical URL of the site. However, when the site is deployed in deploy:review the URL will have changed, because $CI_JOB_ID will be different.

In the past I’ve worked around this by removing the environment config from the build stage, and then just rebuilding the site in deploy:review. In that configuration, the results of build are used in test and then thrown away.

This doesn’t feel right, because it breaks the DevOps rule of building only once for CI/CD, but the $CI_JOB_ID in deploy:review cannot be predicted (I assume?), and and URL of the job artifacts cannot be changed without hosting the artifacts outside of GitLab SASS.

Is there a neat solution to this that I haven’t figured out, or do other people just build twice?