Search
⌘K

Infer Deploy Url

Utilities for inferring deploy url from auto-injected env vars on platform like Vercel or Netlify.

Example Usage

// constants.ts

import { getInferredDeployUrl } from './infer-deploy-url';

export const APP_BASE_URL = getInferredDeployUrl(process.env);
// ⏬ when browser
// ""

// ⏬ when server + no recognized deploy service
// http://localhost:3000

// ⏬ when server + production deploy on vercel
// https://my-project.vercel.app

// ⏬ when server + deploy preview on vercel
// https://my-project-4567g34536d.vercel.app

// ⏬ when server + production deploy on netlify
// https://my-project.netlify.app

// ⏬ when server + deploy preview on netlify
// https://deploy-preview-1--petsof.netlify.app

Dependencies

No dependencies

Auto Install

npx shadcn@latest add https://shadcn-registry-ts.vercel.app/r/util-infer-deploy-url.json

Manual Install

infer-deploy-url.ts

/** Which git branch must be considered `production` */
const GIT_BRANCH_PRODUCTION = 'main';


/**
 * Get the server base URL by inferring the environment.  
 * 
 * If called from `browser` -> returns empty string (relative path usage).  
 * If called from `server` -> try to recognize the URL from env vars injected by the deploy service, if nothing found ->  fallback to `localhost:3000`.  
 * 
 * NOTE: recognized deploy services: `netlify.com`, `vercel.com`
 */
export function getInferredDeployUrl(processEnv: NodeJS.ProcessEnv) {
  // if browser...
  if (isBrowser()) {
    // browser should use relative path
    return '';
  }

  // if is server..
  return (
    inferDeployUrlVercel(processEnv)
    || inferDeployUrlNetlify(processEnv)
    || `http://localhost:${processEnv.PORT ?? 3000}`
  );
}


type InferDeployUrl = (processEnv: NodeJS.ProcessEnv) => string | null;

/** Get Vercel deploy url (`string`) if this app is built on Vercel, or `null` otherwise */
const inferDeployUrlVercel: InferDeployUrl = (processEnv) => {

  // @see https://vercel.com/docs/environment-variables/system-environment-variables
  //
  // - VERCEL=1 if deployed on Vercel
  // - VERCEL_GIT_COMMIT_REF:
  //     - i.e. `main`
  //     - is the git branch of the deployment
  // - VERCEL_PROJECT_PRODUCTION_URL:
  //     - i.e. `my-project.vercel.app`
  //     - is the fixed production url of the project on Vercel
  //     - does not change across deployments
  //     - if custom domain is used, this is the custom domain
  //     - if no custom domain is used, this is the production url auto-assigned by Vercel
  // - VERCEL_URL:
  //     - i.e. `my-project-4535c432342x4234.vercel.app`
  //     - is the immutable deplyment url.
  //     - changes on each deployment

  // if we are on Vercel...
  if (processEnv.VERCEL === '1') {

    // ...and this git branch is PRODUCTION and we have a Vercel Production Url -> return it
    if (processEnv.VERCEL_GIT_COMMIT_REF === GIT_BRANCH_PRODUCTION && processEnv.VERCEL_PROJECT_PRODUCTION_URL) {
      return `https://${processEnv.VERCEL_PROJECT_PRODUCTION_URL}`;
    }

    // ...and this git branch is not PRODUCTION and we have a Vercel Deploy Preview Url or Branch Deploy Url -> return it
    if (processEnv.VERCEL_GIT_COMMIT_REF !== GIT_BRANCH_PRODUCTION && processEnv.VERCEL_URL) {
      return `https://${processEnv.VERCEL_URL}`;
    }
  }

  // if not deployed on Vercel -> return null
  return null;
};

/** Get Netlify deploy url (`string`) if this app is built on Netlify, or `null` otherwise */
const inferDeployUrlNetlify: InferDeployUrl = (processEnv) => {

  // @see https://docs.netlify.com/build/configure-builds/environment-variables/
  //
  // - NETLIFY=true if deployed on netlify
  // - BRANCH:
  //     - i.e. `main`
  //     - is the git branch of the deployment
  // - URL:
  //     - i.e. `https://my-project.netlify.app`
  //     - is the fixed production url of the project on Vercel
  //     - does not change across deployments
  //     - if custom domain is used, this is the custom domain
  //     - if no custom domain is used, this is the auto-assigned by netlify production url
  // - DEPLOY_URL:
  //     - i.e. `https://5b243e66dd6a547b4fee73ae--petsof.netlify.app`
  //     - is the immutable deplyment url.
  //     - changes on each deployment
  // - DEPLOY_PRIME_URL:
  //     - i.e. `https://deploy-preview-1--petsof.netlify.app`
  //     - is the deplyment url of the gruoup (deploy preview for the same PR keep the same url on new commits re-deploy, ...).
  //     - changes when the group changees

  // if we are on Netlify...
  if (processEnv.NETLIFY === 'true') {

    // ...and this git branch is PRODUCTION and we have a Netlify Production Url -> return it
    if (processEnv.BRANCH === 'main' && processEnv.URL) {
      return processEnv.URL;
    }

    // ...and this git branch is not PRODUCTION and we have a Netlify Deploy Preview or Branch Deploy Url -> return it
    if (processEnv.BRANCH !== 'main' && processEnv.DEPLOY_PRIME_URL) {
      return processEnv.DEPLOY_PRIME_URL;
    }
    if (processEnv.BRANCH !== 'main' && processEnv.DEPLOY_URL) {
      return processEnv.DEPLOY_URL;
    }
  }

  // if not deployed on Netlify -> return null
  return null;

};



// utils

function isBrowser() {
  return typeof window !== 'undefined';
}

Test

infer-deploy-url.test.ts
import { describe, it, expect } from 'vitest';

import { getInferredDeployUrl } from './infer-deploy-url';

describe('getInferredDeployUrl', () => {

  it('do it - browser', () => {
    // @ts-ignore
    global.window = {};

    expect(getInferredDeployUrl({
      NODE_ENV: 'production',
    })).toBe("");
    expect(getInferredDeployUrl({
      NODE_ENV: 'development',
    })).toBe("");

    // @ts-ignore
    delete global.window;
  });

  it('do it - server + no recognized deploy service', () => {
    expect(getInferredDeployUrl({
      NODE_ENV: 'production',
    })).toBe("http://localhost:3000");
    expect(getInferredDeployUrl({
      NODE_ENV: 'development',
    })).toBe("http://localhost:3000");
  });

  it('do it - server + vercel', () => {
    // production
    expect(getInferredDeployUrl({
      NODE_ENV: 'production',
      VERCEL: '1',
      VERCEL_GIT_COMMIT_REF: 'main',
      VERCEL_PROJECT_PRODUCTION_URL: 'my-project.vercel.app',
      VERCEL_URL: 'my-project-4535c432342x4234.vercel.app',
    })).toBe('https://my-project.vercel.app');

    // deploy preview
    expect(getInferredDeployUrl({
      NODE_ENV: 'production',
      VERCEL: '1',
      VERCEL_GIT_COMMIT_REF: 'feature-1',
      VERCEL_PROJECT_PRODUCTION_URL: 'my-project.vercel.app',
      VERCEL_URL: 'my-project-4567g34536d.vercel.app',
    })).toBe('https://my-project-4567g34536d.vercel.app');

  });

  it('do it - server + netlify', () => {
    // production
    expect(getInferredDeployUrl({
      NODE_ENV: 'production',
      NETLIFY: 'true',
      BRANCH: 'main',
      URL: 'https://my-project.netlify.app',
      DEPLOY_URL: 'https://5b243e66dd6a547b4fee73ae--petsof.netlify.app',
      DEPLOY_PRIME_URL: 'https://deploy-preview-1--petsof.netlify.app',
    })).toBe('https://my-project.netlify.app');

    // deploy preview
    expect(getInferredDeployUrl({
      NODE_ENV: 'production',
      NETLIFY: 'true',
      BRANCH: 'feature-1',
      URL: 'https://my-project.netlify.app',
      DEPLOY_URL: 'https://5b243e66dd6a547b4fee73ae--petsof.netlify.app',
      DEPLOY_PRIME_URL: 'https://deploy-preview-1--petsof.netlify.app',
    })).toBe('https://deploy-preview-1--petsof.netlify.app');

  });

});

Command Palette

Search for a command to run...