Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(tanstack-start): Fix environment variable loading in runtime contexts by octoper · Pull Request #4743 · clerk/javascript · GitHub
Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(tanstack-start): Fix environment variable loading in runtime contexts by octoper · Pull Request #4743 · clerk/javascript · GitHub
Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(tanstack-start): Fix environment variable loading in runtime contexts by octoper · Pull Request #4743 · clerk/javascript · GitHub
Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(tanstack-start): Fix environment variable loading in runtime contexts by octoper · Pull Request #4743 · clerk/javascript · GitHub
Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(tanstack-start): Fix environment variable loading in runtime contexts by octoper · Pull Request #4743 · clerk/javascript · GitHub
Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(tanstack-start): Fix environment variable loading in runtime contexts by octoper · Pull Request #4743 · clerk/javascript · GitHub
Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(tanstack-start): Fix environment variable loading in runtime contexts by octoper · Pull Request #4743 · clerk/javascript · GitHub
Skip to content
5 changes: 5 additions & 0 deletions .changeset/loud-melons-hope.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/tanstack-start": patch
---

Fix environment variable loading in runtime contexts
39 changes: 15 additions & 24 deletions packages/tanstack-start/src/server/clerkClient.ts
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
import { createClerkClient } from '@clerk/backend';

import {
API_URL,
API_VERSION,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SDK_METADATA,
SECRET_KEY,
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
import { commonEnvs } from './constants';

const clerkClient: typeof createClerkClient = options =>
createClerkClient({
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
const clerkClient: typeof createClerkClient = options => {
const commonEnv = commonEnvs();
return createClerkClient({
secretKey: commonEnv.SECRET_KEY,
publishableKey: commonEnv.PUBLISHABLE_KEY,
apiUrl: commonEnv.API_URL,
apiVersion: commonEnv.API_VERSION,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
proxyUrl: PROXY_URL,
domain: DOMAIN,
isSatellite: IS_SATELLITE,
sdkMetadata: SDK_METADATA,
proxyUrl: commonEnv.PROXY_URL,
domain: commonEnv.DOMAIN,
isSatellite: commonEnv.IS_SATELLITE,
sdkMetadata: commonEnv.SDK_METADATA,
telemetry: {
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
disabled: commonEnv.TELEMETRY_DISABLED,
debug: commonEnv.TELEMETRY_DEBUG,
},
...options,
});
};

export { clerkClient };
51 changes: 31 additions & 20 deletions packages/tanstack-start/src/server/constants.ts
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey';
import { getEvent } from 'vinxi/http';

import { getEnvVariable, getPublicEnvVariables } from '../utils/env';

export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || '';
export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || '';
export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1';
export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || '';
export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || '';
export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || '';
export const API_URL = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY);
export const DOMAIN = getPublicEnvVariables().domain || '';
export const PROXY_URL = getPublicEnvVariables().proxyUrl || '';
export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || '';
export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false;
export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || '';
export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || '';
export const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV'),
};
export const commonEnvs = () => {
const event = getEvent();
const publicEnvs = getPublicEnvVariables(event);

return {
// Public environment variables
CLERK_JS_VERSION: publicEnvs.clerkJsVersion,
CLERK_JS_URL: publicEnvs.clerkJsUrl,
PUBLISHABLE_KEY: publicEnvs.publishableKey,
DOMAIN: publicEnvs.domain,
PROXY_URL: publicEnvs.proxyUrl,
IS_SATELLITE: publicEnvs.isSatellite,
SIGN_IN_URL: publicEnvs.signInUrl,
SIGN_UP_URL: publicEnvs.signUpUrl,
TELEMETRY_DISABLED: publicEnvs.telemetryDisabled,
TELEMETRY_DEBUG: publicEnvs.telemetryDebug,

export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled;
export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug;
// Server-only environment variables
API_VERSION: getEnvVariable('CLERK_API_VERSION', 'v1', event),
SECRET_KEY: getEnvVariable('CLERK_SECRET_KEY', '', event),
ENCRYPTION_KEY: getEnvVariable('CLERK_ENCRYPTION_KEY', '', event),
CLERK_JWT_KEY: getEnvVariable('CLERK_JWT_KEY', '', event),
API_URL: getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publicEnvs.publishableKey),

SDK_METADATA: {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: getEnvVariable('NODE_ENV', '', event),
},
} as const;
};
37 changes: 15 additions & 22 deletions packages/tanstack-start/src/server/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,36 +3,29 @@ import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey
import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { handleValueOrFn } from '@clerk/shared/utils';
import { getEvent } from 'vinxi/http';

import { errorThrower } from '../utils';
import { getEnvVariable, getPublicEnvVariables } from '../utils/env';
import {
CLERK_JWT_KEY,
DOMAIN,
IS_SATELLITE,
PROXY_URL,
PUBLISHABLE_KEY,
SECRET_KEY,
SIGN_IN_URL,
SIGN_UP_URL,
} from './constants';
import { commonEnvs } from './constants';
import type { LoaderOptions } from './types';
import { patchRequest } from './utils';

export const loadOptions = (request: Request, overrides: LoaderOptions = {}) => {
const clerkRequest = createClerkRequest(patchRequest(request));

const secretKey = overrides.secretKey || SECRET_KEY;
const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL);
const signInUrl = overrides.signInUrl || SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl;
const commonEnv = commonEnvs();
const event = getEvent();
const secretKey = overrides.secretKey || commonEnv.SECRET_KEY;
const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY;
const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY;
const apiUrl = getEnvVariable('CLERK_API_URL', '', event) || apiUrlFromPublishableKey(publishableKey);
const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || commonEnv.DOMAIN;
const isSatellite = handleValueOrFn(overrides.isSatellite, new URL(request.url)) || commonEnv.IS_SATELLITE;
const relativeOrAbsoluteProxyUrl = handleValueOrFn(overrides?.proxyUrl, clerkRequest.clerkUrl, commonEnv.PROXY_URL);
const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL;
const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL;
const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(event).afterSignInUrl;
const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(event).afterSignUpUrl;

let proxyUrl;
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
Expand Down
57 changes: 31 additions & 26 deletions packages/tanstack-start/src/utils/env.ts
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
import { isTruthy } from '@clerk/shared/underscore';
import type { HTTPEvent } from 'vinxi/http';

/**
*
* Utility function to get env variables.
*
* @param name env variable name
* @param defaultVaue default value to return if the env variable is not set
* @param event - H3Event object for accessing runtime environment variables
* @returns string
*
* @internal
*/
export const getEnvVariable = (name: string, defaultVaue: string = ''): string => {
export const getEnvVariable = (name: string, defaultValue: string = '', event?: HTTPEvent) => {
// Cloudflare context check
const cfValue = event?.context?.cloudflare?.env[name];
if (cfValue) {
return cfValue;
}
Comment on lines +16 to +20

@wobsorianowobsorianoJan 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitro injects the runtime env vars to the H3 event context. For Cloudflare, the property is event.context.cloudflare.

We pass this event object in a request lifecycle to make runtime env vars available

I answered the same question here.


// Node envs
if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') {
return process.env[name] || defaultVaue;
return process.env[name];
}

// @ts-expect-error - Vite specific
// Vite specific envs
if (typeof import.meta !== 'undefined' && import.meta.env && typeof import.meta.env[name] === 'string') {
// @ts-expect-error - Vite specific
return import.meta.env[name];
}

return defaultVaue;
return defaultValue;
};

export const getPublicEnvVariables = () => {
return {
publishableKey: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
domain: getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'),
isSatellite: isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || isTruthy(getEnvVariable('CLERK_IS_SATELLITE')),
proxyUrl: getEnvVariable('VITE_CLERK_PROXY_URL') || getEnvVariable('CLERK_PROXY_URL'),
pk: getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || getEnvVariable('CLERK_PUBLISHABLE_KEY'),
signInUrl: getEnvVariable('VITE_CLERK_SIGN_IN_URL') || getEnvVariable('CLERK_SIGN_IN_URL'),
signUpUrl: getEnvVariable('VITE_CLERK_SIGN_UP_URL') || getEnvVariable('CLERK_SIGN_UP_URL'),
clerkJsUrl: getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'),
clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || getEnvVariable('CLERK_JS_VARIANT')) as
| ''
| 'headless'
| undefined,
clerkJsVersion: getEnvVariable('VITE_CLERK_JS_VERSION') || getEnvVariable('CLERK_JS_VERSION'),
telemetryDisabled:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')),
telemetryDebug:
isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || getEnvVariable('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || getEnvVariable('CLERK_AFTER_SIGN_UP_URL'),
export const getPublicEnvVariables = (event?: HTTPEvent) => {
const getValue = (name: string): string => {
return getEnvVariable(`VITE_${name}`, '', event) || getEnvVariable(name, '', event);
};

return {
publishableKey: getValue('CLERK_PUBLISHABLE_KEY'),
domain: getValue('CLERK_DOMAIN'),
isSatellite: isTruthy(getValue('CLERK_IS_SATELLITE')),
proxyUrl: getValue('CLERK_PROXY_URL'),
signInUrl: getValue('CLERK_SIGN_IN_URL'),
signUpUrl: getValue('CLERK_SIGN_UP_URL'),
clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'),
clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined,
clerkJsVersion: getValue('CLERK_JS_VERSION'),
telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')),
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
} as const;
};
2 changes: 1 addition & 1 deletion packages/tanstack-start/tsconfig.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,5 +25,5 @@
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
"include": ["src", "vite-env.d.ts"]
}
1 change: 1 addition & 0 deletions packages/tanstack-start/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ export default defineConfig(overrideOptions => {
PACKAGE_VERSION: `"${version}"`,
__DEV__: `${!isProd}`,
},
external: ['vinxi/http'],
};

return runAfterLast([
Expand Down
7 changes: 7 additions & 0 deletions packages/tanstack-start/vite-env.d.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
interface ImportMetaEnv {
[key: string]: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}