Migrate to TanStack Start and pin the Vercel preset

Vercel auto-detected the Hydrogen preset from the @shopify/hydrogen
dependency and failed building a 'hydrogen' Edge Function. The app is now
TanStack Start (SSR via Nitro), and vercel.json pins the tanstack-start
preset and installs with Corepack so Yarn 4 is used.

- Router factory with per-request QueryClient and SSR query dehydration
- Hono API mounted as a Start server route at /api/*
- Account reads in loaders go through server functions (cookies on SSR)
- Loaders prefetch listing pages so products are in the server HTML
- Add graphql peer dependency for gql.tada

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Rami Bitar
2026-10-03 11:35:35 -04:00
co-authored by Claude Opus 5.5
parent c447c51e82
commit 160e65f0ed
26 changed files with 732 additions and 249 deletions
+11 -22
View File
@@ -1,20 +1,15 @@
import { queryOptions } from '@tanstack/react-query';
import type { Customer } from '@/services/shopify/customer';
import {
getAccountCustomer,
getSessionCustomer,
} from '@/server/account.functions';
// The customer token lives in an httpOnly cookie, so account state always comes
// from the Hono API (server/account.ts) rather than from Shopify directly.
// from the server (server functions here; mutations via src/server/account.ts).
export interface SessionCustomer {
displayName: string;
email: string;
firstName?: string | null;
}
async function getJson<T>(url: string): Promise<T> {
const response = await fetch(url, { credentials: 'same-origin' });
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
return response.json();
}
export type SessionCustomer = NonNullable<
Awaited<ReturnType<typeof getSessionCustomer>>
>;
/** Every account query shares this prefix, so sign-in/out can reset them all. */
export const ACCOUNT_QUERY_KEY = ['account'] as const;
@@ -22,20 +17,14 @@ export const ACCOUNT_QUERY_KEY = ['account'] as const;
export const sessionQueryOptions = () =>
queryOptions({
queryKey: [...ACCOUNT_QUERY_KEY, 'me'],
queryFn: () =>
getJson<{ customer: SessionCustomer | null }>('/api/account/me')
.then((data) => data.customer)
// A failed probe reads as signed out.
.catch(() => null),
// A failed probe reads as signed out.
queryFn: () => getSessionCustomer().catch(() => null),
});
export const customerQueryOptions = () =>
queryOptions({
queryKey: [...ACCOUNT_QUERY_KEY, 'customer'],
queryFn: () =>
getJson<{ customer: Customer | null }>('/api/account/customer').then(
(data) => data.customer
),
queryFn: () => getAccountCustomer(),
// Orders should reflect a purchase made a moment ago.
staleTime: 0,
});