Enhance TanStack Start integration and improve user experience

- Updated the default not found component in the TanStack Start demo to provide a user-friendly 404 page.
- Refactored the Header component to use a client-mounted UserButton for better rendering performance.
- Improved cookie handling in the template library to support dynamic imports for TanStack Start server APIs.
- Added a new function to retrieve the server request host, enhancing compatibility with different platforms.

These changes improve the overall integration of TanStack Start, enhance user experience, and ensure better performance across the application.
This commit is contained in:
mantrakp04 2026-04-30 13:06:26 -07:00
parent 1e0e1df407
commit 6703f29d46
5 changed files with 37 additions and 12 deletions

View File

@ -361,6 +361,3 @@ A: Invalid `tools` entries are rejected by `requestBodySchema` in `apps/backend/
## Q: Why did the internal metrics E2E snapshots need to change in April 2026?
A: The `/api/v1/internal/metrics` response now intentionally includes `analytics_overview.daily_anonymous_visitors_fallback`, `analytics_overview.anonymous_visitors_fallback`, and `active_users_by_country`. Those additions are reflected in `packages/stack-shared/src/interface/admin-metrics.ts` and the backend route, so the E2E snapshots must include them instead of treating them as regressions.
## Q: How should a TanStack Start SDK package be added without dragging Dashboard V2 logic into the same PR?
A: Keep the integration PR scoped to generated package registration (`packages/tanstack-start/package.json`, `.gitignore`, `scripts/generate-sdks.ts`, `scripts/utils.ts`), template/package dependency metadata, and SDK runtime changes needed by TanStack Start (`cookie.ts`, token-store handling, handler SSR guard). Leave dashboard routes, hooks, app wiring, and admin API types in the dashboard PR.

View File

@ -1,5 +1,6 @@
import { Link } from "@tanstack/react-router";
import { UserButton } from "@stackframe/tanstack-start";
import { useEffect, useState } from "react";
export function Header() {
return (
@ -14,10 +15,19 @@ export function Header() {
Protected
</Link>
</nav>
<UserButton />
<ClientMountedUserButton />
</div>
</header>
<div className="h-14" />
</>
);
}
function ClientMountedUserButton() {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
return isMounted ? <UserButton /> : <div className="h-9 w-9" />;
}

View File

@ -5,5 +5,14 @@ export function getRouter() {
return createRouter({
routeTree,
scrollRestoration: true,
defaultNotFoundComponent: () => (
<main className="grid min-h-screen place-items-center bg-zinc-100 px-4 text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50">
<div className="w-full max-w-md rounded-lg border border-zinc-200 bg-white p-8 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<p className="mb-2 text-sm font-medium text-zinc-500 dark:text-zinc-400">404</p>
<h1 className="text-2xl font-semibold tracking-tight">Page not found</h1>
<p className="mt-4 text-zinc-600 dark:text-zinc-300">This route is not part of the TanStack Start demo.</p>
</div>
</main>
),
});
}

View File

@ -69,22 +69,23 @@ 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 {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface ImportMetaEnv {
SSR: boolean,
}
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface ImportMeta {
readonly env: ImportMetaEnv,
}
}
async function getTanStackStartServerCookieApi(): Promise<TanStackStartServerCookieApi> {
tanStackStartServerCookieApiPromise ??= import(tanStackStartServerCookieApiImportPath);
tanStackStartServerCookieApiPromise ??= import(/* @vite-ignore */ "@tanstack/react-start/server");
return await tanStackStartServerCookieApiPromise;
}
// END_PLATFORM

View File

@ -84,6 +84,17 @@ const prefetchedCrossDomainHandoffTtlMs = 55 * 60 * 1000;
const allClientApps = new Map<string, [checkString: string | undefined, app: StackClientApp<any, any>]>();
async function getServerRequestHost(): Promise<string | null> {
// IF_PLATFORM next
return (await sc.headers?.())?.get("host") ?? null;
// ELSE_IF_PLATFORM tanstack-start
const api = await import(/* @vite-ignore */ "@tanstack/react-start/server");
return api.getRequestHeader("host") ?? null;
// ELSE_PLATFORM
return null;
// END_PLATFORM
}
type StackClientAppImplConstructorOptionsResolved<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId> & { inheritsFrom?: undefined };
export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, ProjectId extends string = string> implements StackClientApp<HasTokenStore, ProjectId> {
@ -821,12 +832,9 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
let hostname;
if (isBrowserLike()) {
hostname = window.location.hostname;
} else {
hostname = await getServerRequestHost();
}
// IF_PLATFORM next
else {
hostname = (await sc.headers?.())?.get("host");
}
// END_PLATFORM
if (!hostname) {
console.warn("No hostname found when queueing custom refresh cookie update");
return;
@ -1031,7 +1039,7 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
protected _useTokenStore(overrideTokenStoreInit?: TokenStoreInit): Store<TokenObject> {
// IF_PLATFORM tanstack-start
if (!isBrowserLike()) {
return this._getOrCreateTokenStore(use(createCookieHelper()), overrideTokenStoreInit);
return this._getOrCreateTokenStore(use(createCookieHelper()), overrideTokenStoreInit);
}
// END_PLATFORM
suspendIfSsr();