Refactor cookie handling for TanStack Start integration

- Removed the deprecated tanStackStartServerBrowserStub function from the Vite configuration.
- Updated cookie management in the cookie.ts file to support dynamic imports for TanStack Start server APIs.
- Enhanced createCookieHelper to conditionally use TanStack Start's cookie methods based on the SSR environment.
- Adjusted createTanStackStartCookieHelper to accept the server API as a parameter for better modularity.

These changes streamline the integration of TanStack Start, improving cookie management and ensuring compatibility with server-side rendering.
This commit is contained in:
mantrakp04 2026-04-30 12:28:01 -07:00
parent 32ab9c37b6
commit d7f55c1ffd
3 changed files with 46 additions and 55 deletions

View File

@ -48,39 +48,6 @@ function stackSdkSourceTransforms(): Plugin {
};
}
function tanStackStartServerBrowserStub(): Plugin {
const virtualModuleId = "\0tanstack-start-server-browser-stub";
return {
name: "tanstack-start-server-browser-stub",
enforce: "pre",
resolveId(source, _importer, options) {
if (source === "@tanstack/react-start/server" && !options.ssr) {
return virtualModuleId;
}
return null;
},
load(id) {
if (id !== virtualModuleId) {
return null;
}
return `
const throwServerOnly = () => {
throw new Error("@tanstack/react-start/server was called from the browser bundle");
};
export const getCookie = throwServerOnly;
export const getCookies = throwServerOnly;
export const setCookie = throwServerOnly;
export const deleteCookie = throwServerOnly;
export const getRequestHeader = throwServerOnly;
`;
},
};
}
function watchNodeModules(modules: string[]): Plugin {
return {
name: "watch-node-modules",
@ -164,7 +131,6 @@ export default defineConfig({
},
plugins: [
stackSdkSourceTransforms(),
tanStackStartServerBrowserStub(),
waitForWorkspacePackages(["@stackframe/tanstack-start", "@stackframe/stack-shared", "@stackframe/stack-ui"]),
watchNodeModules(["@stackframe/tanstack-start", "@stackframe/stack-shared", "@stackframe/stack-ui"]),
tsConfigPaths(),

View File

@ -1,5 +1,4 @@
import { cookies as rscCookies, headers as rscHeaders } from '@stackframe/stack-sc/force-react-server'; // THIS_LINE_PLATFORM next
import { getCookie as tssGetCookie, getCookies as tssGetCookies, setCookie as tssSetCookie, deleteCookie as tssDeleteCookie, getRequestHeader as tssGetRequestHeader } from '@tanstack/react-start/server'; // THIS_LINE_PLATFORM tanstack-start
import { isBrowserLike } from '@stackframe/stack-shared/dist/utils/env';
import { StackAssertionError } from '@stackframe/stack-shared/dist/utils/errors';
import Cookies from "js-cookie";
@ -68,6 +67,28 @@ import { calculatePKCECodeChallenge, generateRandomCodeVerifier, generateRandomS
type SetCookieOptions = { maxAge: number | "session", noOpIfServerComponent?: boolean, domain?: string, secure?: boolean };
type DeleteCookieOptions = { noOpIfServerComponent?: boolean, domain?: string };
// IF_PLATFORM tanstack-start
type TanStackStartServerCookieApi = typeof import("@tanstack/react-start/server");
const tanStackStartServerCookieApiImportPath = "@tanstack/react-start/server";
let tanStackStartServerCookieApiPromise: Promise<TanStackStartServerCookieApi> | null = null;
let tanStackStartCookieHelperPromise: Promise<CookieHelper> | null = null;
declare global {
interface ImportMetaEnv {
SSR: boolean,
}
interface ImportMeta {
readonly env: ImportMetaEnv,
}
}
async function getTanStackStartServerCookieApi(): Promise<TanStackStartServerCookieApi> {
tanStackStartServerCookieApiPromise ??= import(tanStackStartServerCookieApiImportPath);
return await tanStackStartServerCookieApiPromise;
}
// END_PLATFORM
function ensureClient() {
if (!isBrowserLike()) {
throw new Error("cookieClient functions can only be called in a browser environment, yet window is undefined");
@ -116,7 +137,13 @@ export async function createCookieHelper(): Promise<CookieHelper> {
await rscHeaders(),
);
// ELSE_IF_PLATFORM tanstack-start
return createTanStackStartCookieHelper();
if (import.meta.env.SSR) {
const cookieHelperPromise = tanStackStartCookieHelperPromise
?? getTanStackStartServerCookieApi().then((api) => createTanStackStartCookieHelper(api));
tanStackStartCookieHelperPromise = cookieHelperPromise;
return await cookieHelperPromise;
}
return await createPlaceholderCookieHelper();
// ELSE_PLATFORM
return await createPlaceholderCookieHelper();
// END_PLATFORM
@ -127,9 +154,6 @@ export function createCookieHelperSync(): CookieHelper {
if (isBrowserLike()) {
return createBrowserCookieHelper();
}
// IF_PLATFORM tanstack-start
return createTanStackStartCookieHelper();
// ELSE_PLATFORM
function throwError(): never {
throw new StackAssertionError("Synchronous server cookie helpers are not available on this platform");
}
@ -140,17 +164,16 @@ export function createCookieHelperSync(): CookieHelper {
setOrDelete: throwError,
delete: throwError,
};
// END_PLATFORM
}
// IF_PLATFORM tanstack-start
function determineSecureFromTanStackStartContext(): boolean {
return tssGetRequestHeader("x-forwarded-proto") === "https"
|| (tssGetCookie("stack-is-https") !== undefined);
function determineSecureFromTanStackStartContext(api: TanStackStartServerCookieApi): boolean {
return api.getRequestHeader("x-forwarded-proto") === "https"
|| (api.getCookie("stack-is-https") !== undefined);
}
function refreshTanStackStartIsHttpsCookie() {
tssSetCookie("stack-is-https", "true", {
function refreshTanStackStartIsHttpsCookie(api: TanStackStartServerCookieApi) {
api.setCookie("stack-is-https", "true", {
secure: true,
maxAge: 60 * 60 * 24 * 365,
sameSite: "lax",
@ -158,7 +181,7 @@ function refreshTanStackStartIsHttpsCookie() {
});
}
function createTanStackStartCookieHelper(): CookieHelper {
function createTanStackStartCookieHelper(api: TanStackStartServerCookieApi): CookieHelper {
const helper: CookieHelper = {
get: (name: string) => {
const all = helper.getAll();
@ -166,13 +189,13 @@ function createTanStackStartCookieHelper(): CookieHelper {
},
getAll: () => {
// set a helper cookie, see comment in `NextCookieHelper.set` below
refreshTanStackStartIsHttpsCookie();
return tssGetCookies();
refreshTanStackStartIsHttpsCookie(api);
return api.getCookies();
},
set: (name: string, value: string, options: SetCookieOptions) => {
validateCookieOptions(name, options);
tssSetCookie(name, value, {
secure: requiresSecureAttribute(name) || (options.secure ?? determineSecureFromTanStackStartContext()),
api.setCookie(name, value, {
secure: requiresSecureAttribute(name) || (options.secure ?? determineSecureFromTanStackStartContext(api)),
maxAge: options.maxAge === "session" ? undefined : options.maxAge,
domain: options.domain,
sameSite: "lax",
@ -185,8 +208,8 @@ function createTanStackStartCookieHelper(): CookieHelper {
},
delete: (name: string, options: DeleteCookieOptions) => {
validateCookieOptions(name, options);
const secure = requiresSecureAttribute(name) || determineSecureFromTanStackStartContext();
tssDeleteCookie(name, {
const secure = requiresSecureAttribute(name) || determineSecureFromTanStackStartContext(api);
api.deleteCookie(name, {
secure,
domain: options.domain,
path: "/",
@ -323,7 +346,9 @@ export async function isSecure(): Promise<boolean> {
// IF_PLATFORM next
return determineSecureFromServerContext(await rscCookies(), await rscHeaders());
// ELSE_IF_PLATFORM tanstack-start
return determineSecureFromTanStackStartContext();
if (import.meta.env.SSR) {
return determineSecureFromTanStackStartContext(await getTanStackStartServerCookieApi());
}
// END_PLATFORM
return false;
}

View File

@ -42,7 +42,7 @@ import React, { useCallback, useMemo } from "react"; // THIS_LINE_PLATFORM react
import type * as yup from "yup";
import { constructRedirectUrl } from "../../../../utils/url";
import { getNewOAuthProviderOrScopeUrl, callOAuthCallback } from "../../../auth";
import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createCookieHelperSync, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
import { envVars } from "../../../env";
import { ApiKey, ApiKeyCreationOptions, ApiKeyUpdateOptions, apiKeyCreationOptionsToCrud } from "../../api-keys";
import { ConvexCtx, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, RequestLike, ResolvedHandlerUrls, TokenStoreInit, stackAppInternalsSymbol } from "../../common";
@ -1031,7 +1031,7 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
protected _useTokenStore(overrideTokenStoreInit?: TokenStoreInit): Store<TokenObject> {
// IF_PLATFORM tanstack-start
if (!isBrowserLike()) {
return this._getOrCreateTokenStore(createCookieHelperSync(), overrideTokenStoreInit);
return this._getOrCreateTokenStore(use(createCookieHelper()), overrideTokenStoreInit);
}
// END_PLATFORM
suspendIfSsr();