From 160e65f0ed1d5b8710c0360a5ca2a611d23b325b Mon Sep 17 00:00:00 2001 From: Rami Bitar Date: Sat, 3 Oct 2026 11:35:35 -0400 Subject: [PATCH] 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 --- .env.example | 4 +- .gitignore | 3 + README.md | 59 ++-- index.html | 12 - package.json | 14 +- server/node.ts | 30 -- src/components/ui/sheet.tsx | 2 + src/config/site.ts | 8 +- src/hooks/use-account.ts | 33 +- src/main.tsx | 42 --- src/router.tsx | 43 +++ src/routes/__root.tsx | 36 +- src/routes/account/index.tsx | 3 + src/routes/api/$.ts | 18 + src/routes/collections/index.tsx | 5 + src/routes/index.tsx | 7 + src/routes/search.tsx | 7 + src/routes/shop/collections.tsx | 5 + src/server/account.functions.ts | 36 ++ {server => src/server}/account.ts | 9 - {server => src/server}/app.ts | 3 +- {server => src/server}/session.ts | 0 tsconfig.json | 4 +- vercel.json | 6 + vite.config.ts | 34 +- yarn.lock | 558 ++++++++++++++++++++++++++---- 26 files changed, 732 insertions(+), 249 deletions(-) delete mode 100644 index.html delete mode 100644 server/node.ts delete mode 100644 src/main.tsx create mode 100644 src/router.tsx create mode 100644 src/routes/api/$.ts create mode 100644 src/server/account.functions.ts rename {server => src/server}/account.ts (93%) rename {server => src/server}/app.ts (70%) rename {server => src/server}/session.ts (100%) create mode 100644 vercel.json diff --git a/.env.example b/.env.example index f6af0d5..5fa8033 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ VITE_SHOPIFY_DOMAIN=mock.shop # VITE_SHOPIFY_API_VERSION=2026-07 # Absolute origin used for canonical / Open Graph URLs -# VITE_SITE_URL=http://localhost:5173 +# VITE_SITE_URL=http://localhost:3000 -# Port for `yarn start` +# Port for `yarn start` (Nitro reads PORT) # PORT=3000 diff --git a/.gitignore b/.gitignore index 2f82fbc..d5ce3a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .DS_Store node_modules/* dist +.output +.nitro +.vercel .yarn/install-state.gz .env*.local src/routeTree.gen.ts diff --git a/README.md b/README.md index e186276..6ec5e7b 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,45 @@ # Vite Shopify Storefront -A Shopify storefront built with Vite, React 19, TanStack Router, TanStack Query, -Tailwind CSS v4, Hono and `@shopify/hydrogen`. It's a port of +A server-rendered Shopify storefront built with TanStack Start (Vite + TanStack +Router), TanStack Query, Tailwind CSS v4, Hono and `@shopify/hydrogen` (used +only as a Storefront API client and typed `gql()`). It's a port of `nextjs-templates/shopify-storefront` without the AI store assistant. ## Getting started ```sh cp .env.example .env.local # defaults to the tokenless mock.shop +corepack enable # uses the Yarn 4 pinned in package.json yarn install -yarn dev # http://localhost:5173 +yarn dev # http://localhost:3000 ``` | Script | What it does | | ---------------- | --------------------------------------------------------- | -| `yarn dev` | Vite dev server with the Hono API mounted at `/api` | -| `yarn build` | Client build to `dist/client`, server build to `dist/server` | -| `yarn start` | Production Node server (API + static client), `PORT` or 3000 | +| `yarn dev` | Vite dev server with SSR and the `/api` routes | +| `yarn build` | Production build via Nitro (`.output/`, or Vercel's Build Output on Vercel) | +| `yarn start` | Run the Node build from `.output/` | | `yarn typecheck` | `tsc` plus `hydrogen gql check` for the GraphQL documents | +## Deploying to Vercel + +`vercel.json` pins the **TanStack Start** preset. Without it Vercel's +auto-detection sees `@shopify/hydrogen` and picks the Hydrogen preset. It also +installs with Corepack so Vercel uses Yarn 4. Set the `VITE_SHOPIFY_*` variables +in the project; the production domain is picked up for canonical URLs +automatically, or set `VITE_SITE_URL`. + ## Layout ``` -server/ Hono API — classic customer accounts behind an httpOnly cookie - app.ts /api routes (mounted by @hono/vite-dev-server in dev) - account.ts login, logout, register, recover, reset, activate, me, customer - node.ts production entry: API + dist/client + SPA fallback src/ - routes/ TanStack file routes (route tree is generated) + router.tsx Router + per-request QueryClient, SSR query integration + routes/ File routes; loaders prefetch into TanStack Query + api/$.ts Mounts the Hono app for every /api/* request + server/ + app.ts Hono API + account.ts login, logout, register, recover, reset, activate, me + account.functions.ts server functions for account reads in loaders hooks/ TanStack Query options + hooks over the Storefront API services/shopify Storefront client, catalogue and customer functions graphql/ Typed `gql()` documents @@ -36,17 +48,16 @@ src/ ## How the Next.js pieces map -| Next.js | Here | -| -------------------------------- | ----------------------------------------------------- | -| `app/**/page.tsx` | `src/routes/**` (TanStack file routes) | -| `generateMetadata` / `metadata` | route `head()` via `src/lib/seo.ts` + `` | -| Server-side data fetching | route `loader`s seeding the TanStack Query cache | -| `app/api/account/*` route handlers | `server/account.ts` (Hono) | -| `next/headers` cookies | `hono/cookie` | -| `next/image` | `src/components/ui/image.tsx` (Shopify CDN `srcset`) | -| `useSearchParams` | `validateSearch` + `Route.useSearch()` | -| `NEXT_PUBLIC_*` env | `VITE_*` env | +| Next.js | Here | +| ---------------------------------- | ---------------------------------------------------- | +| `app/**/page.tsx` | `src/routes/**` (TanStack file routes) | +| `generateMetadata` / `metadata` | route `head()` via `src/lib/seo.ts`, server-rendered | +| Server components fetching data | route `loader`s seeding TanStack Query, dehydrated to the client | +| `app/api/account/*` route handlers | `src/server/account.ts` (Hono) | +| `next/headers` cookies | `hono/cookie`, and `getCookie` in server functions | +| `next/image` | `src/components/ui/image.tsx` (Shopify CDN `srcset`) | +| `NEXT_PUBLIC_*` env | `VITE_*` env | -Catalogue and cart requests go straight from the browser to the Storefront API -with the public token. Account requests go through Hono so the customer access -token never reaches client JavaScript. +Cart and catalogue reads after the first page load go straight from the browser +to the Storefront API with the public token. Account requests stay on the +server, so the customer access token never reaches client JavaScript. diff --git a/index.html b/index.html deleted file mode 100644 index 9096d65..0000000 --- a/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Shop - - -
- - - diff --git a/package.json b/package.json index 8177e19..8acb502 100644 --- a/package.json +++ b/package.json @@ -4,26 +4,30 @@ "private": true, "version": "0.0.0", "scripts": { - "dev": "vite", - "build": "vite build && vite build --ssr server/node.ts --outDir dist/server", - "start": "NODE_ENV=production node dist/server/node.js", + "dev": "vite dev", + "build": "vite build", + "start": "node .output/server/index.mjs", "typecheck": "tsc --noEmit && hydrogen gql check" }, "packageManager": "yarn@4.18.0+sha512.fcb8716fe7cd0eece141ffc18b92193a9df9204c1ba83189c288835223fc0bbe64af473bab0d5e9927a7daeb5caf2bb07eb2787cc9338ca040ea125f2a1f2f7e", "dependencies": { "@fontsource-variable/geist": "^5.3.0", "@fontsource-variable/geist-mono": "^5.3.0", - "@hono/node-server": "^2.1.3", "@remixicon/react": "^4.9.0", "@shopify/hydrogen": "0.0.0-preview-116d5d7-20260730141607", + "@tanstack/query-core": "^5.104.1", "@tanstack/react-query": "^5.104.1", "@tanstack/react-router": "^1.170.41", + "@tanstack/react-router-ssr-query": "^1.167.3", + "@tanstack/react-start": "^1.168.60", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "framer-motion": "^12.42.2", + "graphql": "^17.0.2", "hono": "^4.13.12", "lucide-react": "^0.562.0", + "nitro": "3.0.260903-beta", "radix-ui": "^1.6.7", "react": "^19.3.0", "react-dom": "^19.3.0", @@ -31,9 +35,7 @@ "zustand": "^5.0.15" }, "devDependencies": { - "@hono/vite-dev-server": "^0.26.1", "@tailwindcss/vite": "^4.3.3", - "@tanstack/router-plugin": "^1.168.42", "@types/node": "^26.6.4", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", diff --git a/server/node.ts b/server/node.ts deleted file mode 100644 index be9cba9..0000000 --- a/server/node.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Production entry: the API plus the static client build, with an SPA fallback -// so deep links like /products/:handle load index.html. -import { readFile } from 'node:fs/promises'; -import { Hono } from 'hono'; -import { serve } from '@hono/node-server'; -import { serveStatic } from '@hono/node-server/serve-static'; -import api from './app'; - -const CLIENT_DIR = './dist/client'; -const indexHtml = await readFile(`${CLIENT_DIR}/index.html`, 'utf8'); - -const app = new Hono(); - -app.route('/', api); -// Unknown API paths stay JSON 404s rather than falling through to the SPA. -app.all('/api/*', (c) => c.json({ error: 'Not found' }, 404)); - -// Hashed build assets never change, so let browsers keep them. -app.use('/assets/*', async (c, next) => { - await next(); - c.header('Cache-Control', 'public, max-age=31536000, immutable'); -}); -app.use('*', serveStatic({ root: CLIENT_DIR })); -app.get('*', (c) => c.html(indexHtml)); - -const port = Number(process.env.PORT) || 3000; - -serve({ fetch: app.fetch, port }, (info) => { - console.log(`Storefront listening on http://localhost:${info.port}`); -}); diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index 435880e..7980398 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -85,6 +85,8 @@ function SheetTrigger( } function SheetPortal({ children }: { children: React.ReactNode }) { + // Sheets only open after hydration, but never touch `document` on the server. + if (typeof document === 'undefined') return null; return createPortal(children, document.body); } diff --git a/src/config/site.ts b/src/config/site.ts index 3edf261..d915c59 100644 --- a/src/config/site.ts +++ b/src/config/site.ts @@ -2,12 +2,8 @@ export const site = { storeName: 'Shop', description: 'A Shopify storefront built with Vite, TanStack and Hydrogen.', - // Absolute origin for canonical and Open Graph URLs. - url: - import.meta.env.VITE_SITE_URL ?? - (typeof window !== 'undefined' - ? window.location.origin - : 'http://localhost:5173'), + // Absolute origin for canonical and Open Graph URLs (set in vite.config.ts). + url: import.meta.env.VITE_SITE_URL ?? 'http://localhost:3000', logoUrl: '', copyright: '© 2026 Shop. All rights reserved.', // Literal paths (not `string`), so the router's typed links accept them. diff --git a/src/hooks/use-account.ts b/src/hooks/use-account.ts index aa117d3..006169b 100644 --- a/src/hooks/use-account.ts +++ b/src/hooks/use-account.ts @@ -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(url: string): Promise { - 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> +>; /** 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, }); diff --git a/src/main.tsx b/src/main.tsx deleted file mode 100644 index 175e327..0000000 --- a/src/main.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { RouterProvider, createRouter } from '@tanstack/react-router'; -import { routeTree } from './routeTree.gen'; -import { ErrorMessage } from '@/components/route-states'; -import './styles.css'; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - // Catalogue data is fine to reuse for a minute across navigations. - staleTime: 60 * 1000, - refetchOnWindowFocus: false, - }, - }, -}); - -const router = createRouter({ - routeTree, - context: { queryClient }, - // Hovering a link starts its route loader, so most pages open instantly. - defaultPreload: 'intent', - // TanStack Query owns caching; always let loaders ask it. - defaultPreloadStaleTime: 0, - scrollRestoration: true, - defaultErrorComponent: ({ error }) => , -}); - -declare module '@tanstack/react-router' { - interface Register { - router: typeof router; - } -} - -createRoot(document.getElementById('root')!).render( - - - - - -); diff --git a/src/router.tsx b/src/router.tsx new file mode 100644 index 0000000..9700374 --- /dev/null +++ b/src/router.tsx @@ -0,0 +1,43 @@ +import { QueryClient } from '@tanstack/react-query'; +import { createRouter } from '@tanstack/react-router'; +import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'; +import { routeTree } from './routeTree.gen'; +import { ErrorMessage, NotFound } from '@/components/route-states'; + +// Start calls this once per request on the server and once in the browser, so +// each gets its own QueryClient and no cache is shared between visitors. +export function getRouter() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // Catalogue data is fine to reuse for a minute across navigations. + staleTime: 60 * 1000, + refetchOnWindowFocus: false, + }, + }, + }); + + const router = createRouter({ + routeTree, + context: { queryClient }, + // Hovering a link starts its route loader, so most pages open instantly. + defaultPreload: 'intent', + // TanStack Query owns caching; always let loaders ask it. + defaultPreloadStaleTime: 0, + scrollRestoration: true, + defaultErrorComponent: ({ error }) => , + defaultNotFoundComponent: NotFound, + }); + + // Streams queries fetched during SSR to the client cache and wraps the app + // in a QueryClientProvider. + setupRouterSsrQueryIntegration({ router, queryClient }); + + return router; +} + +declare module '@tanstack/react-router' { + interface Register { + router: ReturnType; + } +} diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 742a314..18973dd 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,6 +1,8 @@ +import type { ReactNode } from 'react'; import { HeadContent, Outlet, + Scripts, createRootRouteWithContext, useRouterState, } from '@tanstack/react-router'; @@ -9,16 +11,41 @@ import Header from '@/components/shopify/header'; import Footer from '@/components/shopify/footer'; import { site } from '@/config/site'; import { seo } from '@/lib/seo'; -import { NotFound } from '@/components/route-states'; +import stylesUrl from '@/styles.css?url'; export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()( { - head: () => seo({ title: site.storeName, absolute: true }), + head: () => { + const { meta, links } = seo({ title: site.storeName, absolute: true }); + return { + meta: [ + { charSet: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + ...meta, + ], + links: [{ rel: 'stylesheet', href: stylesUrl }, ...links], + }; + }, + // The document shell renders even when a route errors or 404s. + shellComponent: RootDocument, component: RootLayout, - notFoundComponent: NotFound, } ); +function RootDocument({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ); +} + // Thin bar along the top while a route loader is still running. function NavigationProgress() { const loading = useRouterState({ select: (s) => s.status === 'pending' }); @@ -36,9 +63,8 @@ function NavigationProgress() { function RootLayout() { return ( <> - -
+
{/* Header owns the cart drawer, search and account menu, so every route gets them by rendering here rather than page by page. */}
{ const customer = await queryClient .fetchQuery(customerQueryOptions()) diff --git a/src/routes/api/$.ts b/src/routes/api/$.ts new file mode 100644 index 0000000..36410f0 --- /dev/null +++ b/src/routes/api/$.ts @@ -0,0 +1,18 @@ +import { createFileRoute } from '@tanstack/react-router'; +import api from '@/server/app'; + +// Every /api/* request goes to the Hono app (src/server/app.ts), which owns +// the customer-account endpoints and their session cookie. +const handle = ({ request }: { request: Request }) => api.fetch(request); + +export const Route = createFileRoute('/api/$')({ + server: { + handlers: { + GET: handle, + POST: handle, + PUT: handle, + PATCH: handle, + DELETE: handle, + }, + }, +}); diff --git a/src/routes/collections/index.tsx b/src/routes/collections/index.tsx index 7af5346..7f88602 100644 --- a/src/routes/collections/index.tsx +++ b/src/routes/collections/index.tsx @@ -1,8 +1,13 @@ import { createFileRoute } from '@tanstack/react-router'; import Collections from '@/components/shopify/collections'; +import { collectionsQueryOptions } from '@/hooks/use-shopify-collections'; import { seo } from '@/lib/seo'; export const Route = createFileRoute('/collections/')({ + loader: ({ context: { queryClient } }) => + queryClient + .ensureQueryData(collectionsQueryOptions(12)) + .then(() => undefined, () => undefined), head: () => seo({ title: 'Collections', diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 68de000..18fbb10 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,9 +1,16 @@ import { createFileRoute } from '@tanstack/react-router'; import Products from '@/components/shopify/products'; +import { productsInfiniteQueryOptions } from '@/hooks/use-shopify-products'; import { site } from '@/config/site'; import { seo } from '@/lib/seo'; export const Route = createFileRoute('/')({ + // Server-render the first page of products; the grid reads the same query. + // A failure falls through to the grid's own error state. + loader: ({ context: { queryClient } }) => + queryClient + .ensureInfiniteQueryData(productsInfiniteQueryOptions({ first: 12 })) + .then(() => undefined, () => undefined), // The home page keeps the bare store name rather than "Shop — Shop". head: () => seo({ title: site.storeName, absolute: true, path: '/' }), component: () => ( diff --git a/src/routes/search.tsx b/src/routes/search.tsx index d9755d0..061b2d9 100644 --- a/src/routes/search.tsx +++ b/src/routes/search.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from '@tanstack/react-router'; import SearchResults from '@/components/shopify/search-results'; +import { searchInfiniteQueryOptions } from '@/hooks/use-shopify-search'; import { seo } from '@/lib/seo'; interface SearchParams { @@ -10,6 +11,12 @@ export const Route = createFileRoute('/search')({ validateSearch: (search: Record): SearchParams => ({ q: typeof search.q === 'string' && search.q ? search.q : undefined, }), + loaderDeps: ({ search: { q } }) => ({ q }), + // First page for the default sort and no filters, matching SearchResults. + loader: ({ deps: { q }, context: { queryClient } }) => + queryClient + .ensureInfiniteQueryData(searchInfiniteQueryOptions({ query: q ?? '' })) + .then(() => undefined, () => undefined), head: () => seo({ title: 'Search', diff --git a/src/routes/shop/collections.tsx b/src/routes/shop/collections.tsx index e832785..79404e5 100644 --- a/src/routes/shop/collections.tsx +++ b/src/routes/shop/collections.tsx @@ -1,8 +1,13 @@ import { createFileRoute } from '@tanstack/react-router'; import Collections from '@/components/shopify/collections'; +import { collectionsQueryOptions } from '@/hooks/use-shopify-collections'; import { seo } from '@/lib/seo'; export const Route = createFileRoute('/shop/collections')({ + loader: ({ context: { queryClient } }) => + queryClient + .ensureQueryData(collectionsQueryOptions(12)) + .then(() => undefined, () => undefined), head: () => seo({ title: 'Collections', diff --git a/src/server/account.functions.ts b/src/server/account.functions.ts new file mode 100644 index 0000000..4ea5cd9 --- /dev/null +++ b/src/server/account.functions.ts @@ -0,0 +1,36 @@ +// Server functions for account reads made by route loaders. Loaders run on the +// server during SSR, where `fetch('/api/...')` has neither an origin nor the +// visitor's cookies, so these read the session directly instead. +import { createServerFn } from '@tanstack/react-start'; +import { getCookie } from '@tanstack/react-start/server'; +import { + CUSTOMER_TOKEN_COOKIE, + getCustomer, +} from '@/services/shopify/customer'; + +// Minimal session probe for the header menu — never returns the token. +export const getSessionCustomer = createServerFn({ method: 'GET' }).handler( + async () => { + const token = getCookie(CUSTOMER_TOKEN_COOKIE); + if (!token) return null; + + const customer = await getCustomer(token, 0); + if (!customer) return null; + + return { + displayName: customer.displayName, + email: customer.email, + firstName: customer.firstName, + }; + } +); + +// Full profile with recent orders, for the order history page. +export const getAccountCustomer = createServerFn({ method: 'GET' }).handler( + async () => { + const token = getCookie(CUSTOMER_TOKEN_COOKIE); + if (!token) return null; + + return getCustomer(token, 20); + } +); diff --git a/server/account.ts b/src/server/account.ts similarity index 93% rename from server/account.ts rename to src/server/account.ts index 0b45350..8489264 100644 --- a/server/account.ts +++ b/src/server/account.ts @@ -43,15 +43,6 @@ account.get('/me', async (c) => { }); }); -// Full profile with recent orders, for the order history page. -account.get('/customer', async (c) => { - const token = getSessionToken(c); - if (!token) return c.json({ customer: null }); - - const customer = await getCustomer(token, 20); - return c.json({ customer }); -}); - account.post('/login', async (c) => { const { email, password } = await readBody(c.req.raw); diff --git a/server/app.ts b/src/server/app.ts similarity index 70% rename from server/app.ts rename to src/server/app.ts index 6b27e02..0bac6f4 100644 --- a/server/app.ts +++ b/src/server/app.ts @@ -1,5 +1,4 @@ -// The API surface. In dev, @hono/vite-dev-server mounts this inside Vite; in -// production, server/node.ts serves it alongside the built client. +// The API surface, mounted by the Start server route in routes/api/$.ts. import { Hono } from 'hono'; import { account } from './account'; diff --git a/server/session.ts b/src/server/session.ts similarity index 100% rename from server/session.ts rename to src/server/session.ts diff --git a/tsconfig.json b/tsconfig.json index c20cb1a..06da9e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,6 @@ "@/*": ["./src/*"] } }, - "include": ["src", "server", "vite.config.ts"], - "exclude": ["node_modules", "dist"] + "include": ["src", "vite.config.ts"], + "exclude": ["node_modules", "dist", ".output"] } diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..4ec2022 --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "tanstack-start", + "installCommand": "corepack enable && yarn install", + "buildCommand": "yarn build" +} diff --git a/vite.config.ts b/vite.config.ts index 1a842e0..cc18921 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,30 +1,36 @@ import { defineConfig } from 'vite'; +import { tanstackStart } from '@tanstack/react-start/plugin/vite'; +import { nitro } from 'nitro/vite'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; -import { tanstackRouter } from '@tanstack/router-plugin/vite'; -import devServer from '@hono/vite-dev-server'; import { fileURLToPath, URL } from 'node:url'; +// Absolute origin for canonical and Open Graph URLs. Inlined at build time so +// the server render and the client agree on it. Vercel provides the production +// domain; set VITE_SITE_URL to override it anywhere. +const siteUrl = + process.env.VITE_SITE_URL ?? + (process.env.VERCEL_PROJECT_PRODUCTION_URL + ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}` + : 'http://localhost:3000'); + export default defineConfig({ + server: { port: 3000 }, plugins: [ - // Must run before the React plugin so the generated route tree is in place. - tanstackRouter({ target: 'react', autoCodeSplitting: true }), + // Start must come before the React plugin. + tanstackStart(), + // Builds the server for the host: Vercel's Build Output when deployed + // there, a standalone Node server (.output/) everywhere else. + nitro(), react(), tailwindcss(), - // Serves the Hono API in dev. Everything outside /api falls through to - // Vite, which serves the SPA. - devServer({ - entry: 'server/app.ts', - exclude: [/^(?!\/api(\/|$)).*/], - injectClientScript: false, - }), ], + define: { + 'import.meta.env.VITE_SITE_URL': JSON.stringify(siteUrl), + }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, - build: { - outDir: 'dist/client', - }, }); diff --git a/yarn.lock b/yarn.lock index 175218e..d8f4068 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30,7 +30,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.29.7": +"@babel/code-frame@npm:7.29.7, @babel/code-frame@npm:^7.29.7": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" dependencies: @@ -331,45 +331,6 @@ __metadata: languageName: node linkType: hard -"@hono/node-server@npm:^1.19.11": - version: 1.19.17 - resolution: "@hono/node-server@npm:1.19.17" - peerDependencies: - hono: ^4 - checksum: 10c0/e77907e3b5db8ab68ed4f297ad84807bfdd698636408de86ab8d36dab4658504ac4c0ed9d9436bc272a0172b9b7a5a4b578532e0b5baf97674c7b420cb5cff4b - languageName: node - linkType: hard - -"@hono/node-server@npm:^2.1.3": - version: 2.1.3 - resolution: "@hono/node-server@npm:2.1.3" - peerDependencies: - hono: ^4 - checksum: 10c0/1b9996f41be620c301f73529928ccb056574fadf822a0979e4925c17cce209583e7f27376ae2308b4e87ab6c1c7501d6f282333c364ed79f21b99b45b47f7ffb - languageName: node - linkType: hard - -"@hono/vite-dev-server@npm:^0.26.1": - version: 0.26.1 - resolution: "@hono/vite-dev-server@npm:0.26.1" - dependencies: - "@hono/node-server": "npm:^1.19.11" - minimatch: "npm:^9.0.3" - peerDependencies: - hono: "*" - miniflare: "*" - wrangler: "*" - peerDependenciesMeta: - hono: - optional: false - miniflare: - optional: true - wrangler: - optional: true - checksum: 10c0/235334d35c1f6345a7e874191c2a18f2e2018a0713ed8fcef5f0c9aa3097fc0cc15ae6bb95f9536b75fc4b5b1bf4421140e857ffaa87029c4b692c893caa9a2b - languageName: node - linkType: hard - "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -435,6 +396,43 @@ __metadata: languageName: node linkType: hard +"@oozcitak/dom@npm:^2.0.2": + version: 2.0.2 + resolution: "@oozcitak/dom@npm:2.0.2" + dependencies: + "@oozcitak/infra": "npm:^2.0.2" + "@oozcitak/url": "npm:^3.0.0" + "@oozcitak/util": "npm:^10.0.0" + checksum: 10c0/053dfec09c6b5a38e2deb601f97313e01184a2c6452c13767a9629aa6686749f6f9d230806c51ab213dd454465b9e0f403cb7c45e210b447d5fe059a780278cf + languageName: node + linkType: hard + +"@oozcitak/infra@npm:^2.0.2": + version: 2.0.2 + resolution: "@oozcitak/infra@npm:2.0.2" + dependencies: + "@oozcitak/util": "npm:^10.0.0" + checksum: 10c0/26392f3cc6c0787b9256e1eaa39a4dc374dc54f437ea1e8d3314fb41f7bc2c011c3c3eac7bc58b1773968df0c2991ca41761aa12778a0ca4059f0a79002b6205 + languageName: node + linkType: hard + +"@oozcitak/url@npm:^3.0.0": + version: 3.0.0 + resolution: "@oozcitak/url@npm:3.0.0" + dependencies: + "@oozcitak/infra": "npm:^2.0.2" + "@oozcitak/util": "npm:^10.0.0" + checksum: 10c0/0a43eb9db072078f0826ea5eeefd5c398e4e47aa6f99a09b87724eed3bf018894de3e6617631547df194ec0ceb965cd5feb9bf666eb094952fed1280770b4f7a + languageName: node + linkType: hard + +"@oozcitak/util@npm:^10.0.0": + version: 10.0.0 + resolution: "@oozcitak/util@npm:10.0.0" + checksum: 10c0/4f143f50779790166e747c722383551af819776e7f946c9bd25b9875a128c517c91f058fb4ea11b235388eea83bf7b84a5590f882c74171e95b04487bc2b948d + languageName: node + linkType: hard + "@oxc-project/types@npm:=0.152.0": version: 0.152.0 resolution: "@oxc-project/types@npm:0.152.0" @@ -2047,7 +2045,7 @@ __metadata: languageName: node linkType: hard -"@tanstack/query-core@npm:5.104.1": +"@tanstack/query-core@npm:5.104.1, @tanstack/query-core@npm:^5.104.1": version: 5.104.1 resolution: "@tanstack/query-core@npm:5.104.1" checksum: 10c0/d42788e0c9ee4a389d7de21c5fe856acd50d24f72cc87b3c76e6852399ab9835801f415d0a9e10706d2a3a27937d42c5f1ac621ceeac21e6bcd60aa87984bdf4 @@ -2065,7 +2063,22 @@ __metadata: languageName: node linkType: hard -"@tanstack/react-router@npm:^1.170.41": +"@tanstack/react-router-ssr-query@npm:^1.167.3": + version: 1.167.3 + resolution: "@tanstack/react-router-ssr-query@npm:1.167.3" + dependencies: + "@tanstack/router-ssr-query-core": "npm:1.169.3" + peerDependencies: + "@tanstack/query-core": ">=5.102.0" + "@tanstack/react-query": ">=5.102.0" + "@tanstack/react-router": ">=1.170.33" + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" + checksum: 10c0/242a1f98660d8b9d6d474a5177234b26c24a87de015ab8592654581f9b49538f1010e15183d94491680d00a6a7627ac345c9b38c5420d5dba50ca8ddc7d39a38 + languageName: node + linkType: hard + +"@tanstack/react-router@npm:1.170.41, @tanstack/react-router@npm:^1.170.41": version: 1.170.41 resolution: "@tanstack/react-router@npm:1.170.41" dependencies: @@ -2080,6 +2093,92 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-start-client@npm:1.168.39": + version: 1.168.39 + resolution: "@tanstack/react-start-client@npm:1.168.39" + dependencies: + "@tanstack/react-router": "npm:1.170.41" + "@tanstack/router-core": "npm:1.171.34" + "@tanstack/start-client-core": "npm:1.170.34" + peerDependencies: + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" + checksum: 10c0/710142fcaaaef7ed40c26bd2b874c5cbe5544d57ce2134d226a4057742b0c276c6d92902b119abae07d1a53ed95d3dabe2fca47c4d844390052889c671658b48 + languageName: node + linkType: hard + +"@tanstack/react-start-rsc@npm:0.1.59": + version: 0.1.59 + resolution: "@tanstack/react-start-rsc@npm:0.1.59" + dependencies: + "@tanstack/react-router": "npm:1.170.41" + "@tanstack/router-core": "npm:1.171.34" + "@tanstack/router-utils": "npm:1.162.3" + "@tanstack/start-client-core": "npm:1.170.34" + "@tanstack/start-fn-stubs": "npm:1.162.0" + "@tanstack/start-plugin-core": "npm:1.171.49" + "@tanstack/start-storage-context": "npm:1.167.36" + pathe: "npm:^2.0.3" + peerDependencies: + "@rspack/core": ">=2.0.0-0" + "@vitejs/plugin-rsc": ">=0.5.30" + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" + react-server-dom-rspack: ">=0.0.2" + peerDependenciesMeta: + "@rspack/core": + optional: true + "@vitejs/plugin-rsc": + optional: true + react-server-dom-rspack: + optional: true + checksum: 10c0/251068c34d35bac4002d3824fe7fb6d0fd1807de27c7c9befa5b9c0ebd698f67f862ec0d05d6f652f8d9f2af59af6d2fdb77568dfd20a9a6c432238e5bac2379 + languageName: node + linkType: hard + +"@tanstack/react-start-server@npm:1.167.46": + version: 1.167.46 + resolution: "@tanstack/react-start-server@npm:1.167.46" + dependencies: + "@tanstack/react-router": "npm:1.170.41" + "@tanstack/router-core": "npm:1.171.34" + "@tanstack/start-server-core": "npm:1.169.39" + peerDependencies: + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" + checksum: 10c0/6d016f0749e1f3d4f9a11b68b287b99e9cb4fe16f1cdb76525f1d3d5e048e96cd8a3c95348ab4b254a32d83a379e75a5274eb0b6be5acb23765157400052950e + languageName: node + linkType: hard + +"@tanstack/react-start@npm:^1.168.60": + version: 1.168.60 + resolution: "@tanstack/react-start@npm:1.168.60" + dependencies: + "@tanstack/react-router": "npm:1.170.41" + "@tanstack/react-start-client": "npm:1.168.39" + "@tanstack/react-start-rsc": "npm:0.1.59" + "@tanstack/react-start-server": "npm:1.167.46" + "@tanstack/router-utils": "npm:1.162.3" + "@tanstack/start-client-core": "npm:1.170.34" + "@tanstack/start-plugin-core": "npm:1.171.49" + "@tanstack/start-server-core": "npm:1.169.39" + pathe: "npm:^2.0.3" + peerDependencies: + "@rsbuild/core": ^2.0.0 + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" + vite: ">=7.0.0" + peerDependenciesMeta: + "@rsbuild/core": + optional: true + "@vitejs/plugin-rsc": + optional: true + vite: + optional: true + checksum: 10c0/905c23a737775e88937f8fdd1f6bdf468f5c21bdc2d88712148435246eb0eb1d5d90d083878988404be25573b4e3284fb8a391efdbc0d33195891090bfd8ab3d + languageName: node + linkType: hard + "@tanstack/react-store@npm:^0.11.2": version: 0.11.2 resolution: "@tanstack/react-store@npm:0.11.2" @@ -2121,7 +2220,7 @@ __metadata: languageName: node linkType: hard -"@tanstack/router-plugin@npm:^1.168.42": +"@tanstack/router-plugin@npm:1.168.42": version: 1.168.42 resolution: "@tanstack/router-plugin@npm:1.168.42" dependencies: @@ -2155,6 +2254,16 @@ __metadata: languageName: node linkType: hard +"@tanstack/router-ssr-query-core@npm:1.169.3": + version: 1.169.3 + resolution: "@tanstack/router-ssr-query-core@npm:1.169.3" + peerDependencies: + "@tanstack/query-core": ">=5.102.0" + "@tanstack/router-core": ">=1.171.28" + checksum: 10c0/c5e77b2ac7697d8f35b1515e2f8a29526aecde48ff23aa00a70f43970cb6b02887c9e034eadb91c9d405d87ecf88a53595c0e42a342411bb64d5ddce1a808860 + languageName: node + linkType: hard + "@tanstack/router-utils@npm:1.162.3": version: 1.162.3 resolution: "@tanstack/router-utils@npm:1.162.3" @@ -2171,6 +2280,86 @@ __metadata: languageName: node linkType: hard +"@tanstack/start-client-core@npm:1.170.34": + version: 1.170.34 + resolution: "@tanstack/start-client-core@npm:1.170.34" + dependencies: + "@tanstack/router-core": "npm:1.171.34" + "@tanstack/start-fn-stubs": "npm:1.162.0" + "@tanstack/start-storage-context": "npm:1.167.36" + seroval: "npm:^1.6.7" + checksum: 10c0/f11adf1514db4f9dec7f8a78bb76fc3eb063724ba1ad74e03eadac4f3611743300ec47ca3775feecc3f67d67de08630542ffcda2bbae0f9239e34706af0aff97 + languageName: node + linkType: hard + +"@tanstack/start-fn-stubs@npm:1.162.0": + version: 1.162.0 + resolution: "@tanstack/start-fn-stubs@npm:1.162.0" + checksum: 10c0/74a29594f38204e547871d201abf6dd297adf58754ed39ef8fa2a6bea6a2999386445023e50dba8fbe013bbb08f5d5cbd3f96b9301e6a8c8e4cf6047c24ca1f0 + languageName: node + linkType: hard + +"@tanstack/start-plugin-core@npm:1.171.49": + version: 1.171.49 + resolution: "@tanstack/start-plugin-core@npm:1.171.49" + dependencies: + "@babel/code-frame": "npm:7.29.7" + "@babel/core": "npm:^7.29.7" + "@babel/types": "npm:^7.29.8" + "@jridgewell/remapping": "npm:^2.3.5" + "@tanstack/router-core": "npm:1.171.34" + "@tanstack/router-generator": "npm:1.167.40" + "@tanstack/router-plugin": "npm:1.168.42" + "@tanstack/router-utils": "npm:1.162.3" + "@tanstack/start-server-core": "npm:1.169.39" + exsolve: "npm:^1.1.1" + lightningcss: "npm:^1.33.0" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.7" + seroval: "npm:^1.6.7" + source-map: "npm:^0.7.6" + srvx: "npm:^0.11.22" + tinyglobby: "npm:^0.2.17" + ufo: "npm:^1.6.4" + vitefu: "npm:^1.1.3" + xmlbuilder2: "npm:^4.0.3" + zod: "npm:^4.5.4" + peerDependencies: + "@rsbuild/core": ^2.0.0 + vite: ">=7.0.0" + peerDependenciesMeta: + "@rsbuild/core": + optional: true + vite: + optional: true + checksum: 10c0/c10e2bcbb70661fda8ce3e3f273b1904600e6b0d45d6f50b5c52a5e72148a153e6d43a7a28411415729b19a29367cf76b07ee68bfafe0484f3717e5f358e0096 + languageName: node + linkType: hard + +"@tanstack/start-server-core@npm:1.169.39": + version: 1.169.39 + resolution: "@tanstack/start-server-core@npm:1.169.39" + dependencies: + "@tanstack/history": "npm:1.162.4" + "@tanstack/router-core": "npm:1.171.34" + "@tanstack/start-client-core": "npm:1.170.34" + "@tanstack/start-storage-context": "npm:1.167.36" + fetchdts: "npm:^0.1.6" + h3-v2: "npm:h3@2.0.1-rc.20" + seroval: "npm:^1.6.7" + checksum: 10c0/fd4d123eabb5fbaa07f2a6a2fc03d6244114e9214d0c3ad46a60f6c8223dffe9e11907ec08adbc63a7829f7ad10f150caf829ac674db85d6290f5ba6e7adc14b + languageName: node + linkType: hard + +"@tanstack/start-storage-context@npm:1.167.36": + version: 1.167.36 + resolution: "@tanstack/start-storage-context@npm:1.167.36" + dependencies: + "@tanstack/router-core": "npm:1.171.34" + checksum: 10c0/7714b8dbc41c7820542d264f38c2225991811ccb51254b88880ffb8d63ffa478c266a3edc7a78d1b2c6d813de0b0269de7bed63549322dd274d2143860680fce + languageName: node + linkType: hard + "@tanstack/store@npm:0.11.2": version: 0.11.2 resolution: "@tanstack/store@npm:0.11.2" @@ -2256,6 +2445,13 @@ __metadata: languageName: node linkType: hard +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + "aria-hidden@npm:^1.2.4": version: 1.2.6 resolution: "aria-hidden@npm:1.2.6" @@ -2277,13 +2473,6 @@ __metadata: languageName: node linkType: hard -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - "baseline-browser-mapping@npm:^2.11.26": version: 2.11.27 resolution: "baseline-browser-mapping@npm:2.11.27" @@ -2293,15 +2482,6 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^2.0.2": - version: 2.1.7 - resolution: "brace-expansion@npm:2.1.7" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/17ee4194c85b05cde0a7b7c068eeef664440c027116ef42c3d35c13ebabdc9fd3eaf05c5c8a06dd0ca20ae012d652561d8ac2a699daa5cbee1f0510e5134ab4d - languageName: node - linkType: hard - "browserslist@npm:^4.24.0": version: 4.29.3 resolution: "browserslist@npm:4.29.3" @@ -2371,6 +2551,13 @@ __metadata: languageName: node linkType: hard +"consola@npm:^3.4.2": + version: 3.4.2 + resolution: "consola@npm:3.4.2" + checksum: 10c0/7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a + languageName: node + linkType: hard + "convert-source-map@npm:^2.0.0": version: 2.0.0 resolution: "convert-source-map@npm:2.0.0" @@ -2385,6 +2572,18 @@ __metadata: languageName: node linkType: hard +"crossws@npm:^0.4.12": + version: 0.4.12 + resolution: "crossws@npm:0.4.12" + peerDependencies: + srvx: ">=0.11.5" + peerDependenciesMeta: + srvx: + optional: true + checksum: 10c0/28d95429d666e2b925ccdc1c2f01ad23de21b7a8772cf063281127b9eea4a1b80c459f8404957d7231609750ff81ed6a6514e9c393a3130f35919e7a98a854ce + languageName: node + linkType: hard + "csstype@npm:^3.2.2": version: 3.2.3 resolution: "csstype@npm:3.2.3" @@ -2392,6 +2591,13 @@ __metadata: languageName: node linkType: hard +"db0@npm:^0.4.1": + version: 0.4.1 + resolution: "db0@npm:0.4.1" + checksum: 10c0/2f965e59496455e2c8105f7f17bbbb8a93aefbe6d023cb6670ccba4757ee3ea99ae45be9b190ce87a98257d75a11fa98d1fbe506757360f7dc57adb9465c953e + languageName: node + linkType: hard + "debug@npm:^4.1.0, debug@npm:^4.3.1": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -2449,6 +2655,20 @@ __metadata: languageName: node linkType: hard +"env-runner@npm:^0.2.1": + version: 0.2.3 + resolution: "env-runner@npm:0.2.3" + dependencies: + crossws: "npm:^0.4.12" + exsolve: "npm:^1.1.1" + httpxy: "npm:^0.5.5" + srvx: "npm:^1.0.5" + bin: + env-runner: ./dist/cli.mjs + checksum: 10c0/b72e0cf4dc17257b963c4dd265749bf68eea721558d82bcedb7b0684d946c8624b8c57949485f54bef86fea92a3fe5abce66cd1e3b70abcb8177950079fb5f0d + languageName: node + linkType: hard + "escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -2463,6 +2683,13 @@ __metadata: languageName: node linkType: hard +"exsolve@npm:^1.1.1": + version: 1.1.1 + resolution: "exsolve@npm:1.1.1" + checksum: 10c0/0b157a7600408bd356ff78cff5ad9c8c0dd7ca5e7f246216c3c9b66196e76277d3130021122b9c5078a3462751fd7aa988fe3dc1e3b80c79cb873c1623f628e1 + languageName: node + linkType: hard + "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -2475,6 +2702,13 @@ __metadata: languageName: node linkType: hard +"fetchdts@npm:^0.1.6": + version: 0.1.7 + resolution: "fetchdts@npm:0.1.7" + checksum: 10c0/7497671ee28b3e7cc49d037a7c11e6f98e53e6e2abd85f26e8fac3fae4142c3c0d91352dc46198530f73b16480585605c63e3d112c34210ff2c75be1199668a0 + languageName: node + linkType: hard + "framer-motion@npm:^12.42.2": version: 12.43.0 resolution: "framer-motion@npm:12.43.0" @@ -2554,13 +2788,50 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^15.5.0 || ^16.0.0 || ^17.0.0": +"graphql@npm:^15.5.0 || ^16.0.0 || ^17.0.0, graphql@npm:^17.0.2": version: 17.0.2 resolution: "graphql@npm:17.0.2" checksum: 10c0/766546216b891439ed09fd8584963df2013e26f2f79f096036eaaa2788c38964e049c8a142d68e3afdabdd8bbe70042639e83f0f4b82edbc7c6850421f072bb6 languageName: node linkType: hard +"h3-v2@npm:h3@2.0.1-rc.20": + version: 2.0.1-rc.20 + resolution: "h3@npm:2.0.1-rc.20" + dependencies: + rou3: "npm:^0.8.1" + srvx: "npm:^0.11.13" + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + bin: + h3: bin/h3.mjs + checksum: 10c0/9bc662b9b61450625cacb9dcbe8c6d3b86281090f3816ea6c70fad854a24dbb7dcb3450c987f00e71960ee930daf95453253a855c5e1ea8a13e3cd7c3b2113f6 + languageName: node + linkType: hard + +"h3@npm:^2.0.1-rc.31": + version: 2.0.1-rc.32 + resolution: "h3@npm:2.0.1-rc.32" + dependencies: + rou3: "npm:^0.9.2" + srvx: "npm:^1.0.5" + peerDependencies: + crossws: ^0.4.12 + ocache: ">=0.3.0" + peerDependenciesMeta: + crossws: + optional: true + ocache: + optional: true + bin: + h3: ./bin/h3.mjs + checksum: 10c0/182881ebf9c866f79ca120e8bc1b8a8850c34673758b67593fed7217cf04387294241e63397e20dd690c39cad05c6ce070eefc0707d7813ecfa9e358c66edd59 + languageName: node + linkType: hard + "hono@npm:^4.13.12": version: 4.13.12 resolution: "hono@npm:4.13.12" @@ -2568,6 +2839,20 @@ __metadata: languageName: node linkType: hard +"hookable@npm:^6.1.1": + version: 6.1.2 + resolution: "hookable@npm:6.1.2" + checksum: 10c0/ea5cdb1b1a1919b48cfbbfb55c8009f6843473ff50f67d6e959fd291cb2fd0a4934e85124bfd6531b1d2edf1e0675bc5954f85a12318a59b15c7a3e5667f0d4e + languageName: node + linkType: hard + +"httpxy@npm:^0.5.5": + version: 0.5.5 + resolution: "httpxy@npm:0.5.5" + checksum: 10c0/7be52cbfbd4157b4ac3aa22124d06081e8ffc50a185622e43c0ce8229bbcd7411d016f4a364fecb694325ed312823efbf51c70fa8049563a8b6b617642914cd3 + languageName: node + linkType: hard + "isbot@npm:^5.1.22": version: 5.2.2 resolution: "isbot@npm:5.2.2" @@ -2598,6 +2883,17 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^4.1.1": + version: 4.3.2 + resolution: "js-yaml@npm:4.3.2" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/dedd34c2e8fef1d504687f1bc94ed0ee1311a1f78770fa2800352c6d59c635ccf555009d74e9afe529ae62199da2b1ccef30e53dc84dcd8d2d8187c22574bc97 + languageName: node + linkType: hard + "jsesc@npm:^3.0.2": version: 3.1.0 resolution: "jsesc@npm:3.1.0" @@ -2883,15 +3179,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.3": - version: 9.0.9 - resolution: "minimatch@npm:9.0.9" - dependencies: - brace-expansion: "npm:^2.0.2" - checksum: 10c0/0b6a58530dbb00361745aa6c8cffaba4c90f551afe7c734830bd95fd88ebf469dd7355a027824ea1d09e37181cfeb0a797fb17df60c15ac174303ac110eb7e86 - languageName: node - linkType: hard - "minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.3 resolution: "minipass@npm:7.1.3" @@ -2940,6 +3227,36 @@ __metadata: languageName: node linkType: hard +"nf3@npm:^0.3.24": + version: 0.3.24 + resolution: "nf3@npm:0.3.24" + checksum: 10c0/6c971b63634b6f7838194b5266fb40a8cb763d55a83b07a58ec3cf044fba1eef2fddfb6ee4d760ac3fbda34f69b9c04ddb923505cff3bbb57b7836c33c54b8ed + languageName: node + linkType: hard + +"nitro@npm:3.0.260903-beta": + version: 3.0.260903-beta + resolution: "nitro@npm:3.0.260903-beta" + dependencies: + consola: "npm:^3.4.2" + crossws: "npm:^0.4.12" + db0: "npm:^0.4.1" + env-runner: "npm:^0.2.1" + h3: "npm:^2.0.1-rc.31" + hookable: "npm:^6.1.1" + nf3: "npm:^0.3.24" + ocache: "npm:^0.3.0" + rolldown: "npm:^1.2.7" + rou3: "npm:^0.9.2" + srvx: "npm:^1.0.3" + unenv: "npm:^2.0.0-rc.24" + unstorage: "npm:^2.0.0-alpha.10" + bin: + nitro: dist/cli/index.mjs + checksum: 10c0/7947c389578c42333b5e8e2bf21e275c5396cb6a9e25ca87ff80c108d199a5a2aa60e08aad369d67c853f866aa4eb355b5e3ca985f17df67dad90f5449a74754 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 13.1.0 resolution: "node-gyp@npm:13.1.0" @@ -2978,6 +3295,13 @@ __metadata: languageName: node linkType: hard +"ocache@npm:^0.3.0": + version: 0.3.0 + resolution: "ocache@npm:0.3.0" + checksum: 10c0/1caf0f8d24ef638dde0c4a5301376c7c1d0f44f4e7ed5430012c1983d0a1af6436b5243c3adf9c85ec0d4f33c1a2ab211351fe55337cd7308abe190557f37bbd + languageName: node + linkType: hard + "pathe@npm:^2.0.3": version: 2.0.3 resolution: "pathe@npm:2.0.3" @@ -3175,7 +3499,7 @@ __metadata: languageName: node linkType: hard -"rolldown@npm:~1.2.11": +"rolldown@npm:^1.2.7, rolldown@npm:~1.2.11": version: 1.2.12 resolution: "rolldown@npm:1.2.12" dependencies: @@ -3233,6 +3557,20 @@ __metadata: languageName: node linkType: hard +"rou3@npm:^0.8.1": + version: 0.8.1 + resolution: "rou3@npm:0.8.1" + checksum: 10c0/c8728cf3c41833db0e20cbadba07b3c678b8b9fb12db1d8803f275a7a6cce02d0be9bee79367575883f65659c9c0ed1001e6527146ed27772e439e5d6c68d264 + languageName: node + linkType: hard + +"rou3@npm:^0.9.2": + version: 0.9.2 + resolution: "rou3@npm:0.9.2" + checksum: 10c0/4b0ab14fbd3a7c60ffee1af644f618a9e76dd218cd4fbb585b8fa3601a3f25857c2b1656a215389379a7aef1e1e1897fd55e835658e9c3a5d9683571b36b3bd6 + languageName: node + linkType: hard + "scheduler@npm:^0.28.0": version: 0.28.0 resolution: "scheduler@npm:0.28.0" @@ -3281,6 +3619,31 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.7.6": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c + languageName: node + linkType: hard + +"srvx@npm:^0.11.13, srvx@npm:^0.11.22": + version: 0.11.22 + resolution: "srvx@npm:0.11.22" + bin: + srvx: bin/srvx.mjs + checksum: 10c0/aae81d7aa2d43e23fd483088533ad471ee2cd28f2065b84119635fd9feabdd4bab94fc06b8d198c205e3613b552624ccd2015c887ba9ca415d9c51dde7e6ec79 + languageName: node + linkType: hard + +"srvx@npm:^1.0.3, srvx@npm:^1.0.5": + version: 1.0.5 + resolution: "srvx@npm:1.0.5" + bin: + srvx: ./bin/srvx.mjs + checksum: 10c0/7ff87707840af95b399a11ab0b2fba6ded66a1d4658324913a1c95c916304056e5e618a9fc93803c1351aaf6acf4f7f3c5f55f18e82110a2ce876246d770c881 + languageName: node + linkType: hard + "tailwind-merge@npm:^3.7.0": version: 3.7.0 resolution: "tailwind-merge@npm:3.7.0" @@ -3352,6 +3715,13 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.6.4": + version: 1.6.4 + resolution: "ufo@npm:1.6.4" + checksum: 10c0/3a2b29e7e3d772fbf6893d7d23bf442981457adb2fe122828abdbda89bedcb81aafd0dcc080e41b45f9a877db00cb42cbfee9639753a19d9b9bd39b5627039cf + languageName: node + linkType: hard + "undici-types@npm:~8.9.0": version: 8.9.0 resolution: "undici-types@npm:8.9.0" @@ -3366,6 +3736,15 @@ __metadata: languageName: node linkType: hard +"unenv@npm:^2.0.0-rc.24": + version: 2.0.0-rc.24 + resolution: "unenv@npm:2.0.0-rc.24" + dependencies: + pathe: "npm:^2.0.3" + checksum: 10c0/e8556b4287fcf647f23db790eea2782cc79f182370718680e3aba4753d5fb7177abf5d6df489c8f74f7e3ad6cd554b8623cc01caf3e6f2d5548e69178adb1691 + languageName: node + linkType: hard + "unplugin@npm:^3.3.0": version: 3.4.0 resolution: "unplugin@npm:3.4.0" @@ -3409,6 +3788,13 @@ __metadata: languageName: node linkType: hard +"unstorage@npm:^2.0.0-alpha.10": + version: 2.0.0-alpha.10 + resolution: "unstorage@npm:2.0.0-alpha.10" + checksum: 10c0/60473155ac6900020462f4d4192ff8a300cd510e8739f7b1905faece74fe0438b01cf00a84bcda81166cc3344838492e352998e1c48d8bc1449b1191d82ed3f7 + languageName: node + linkType: hard + "update-browserslist-db@npm:^1.3.3": version: 1.3.3 resolution: "update-browserslist-db@npm:1.3.3" @@ -3469,14 +3855,14 @@ __metadata: dependencies: "@fontsource-variable/geist": "npm:^5.3.0" "@fontsource-variable/geist-mono": "npm:^5.3.0" - "@hono/node-server": "npm:^2.1.3" - "@hono/vite-dev-server": "npm:^0.26.1" "@remixicon/react": "npm:^4.9.0" "@shopify/hydrogen": "npm:0.0.0-preview-116d5d7-20260730141607" "@tailwindcss/vite": "npm:^4.3.3" + "@tanstack/query-core": "npm:^5.104.1" "@tanstack/react-query": "npm:^5.104.1" "@tanstack/react-router": "npm:^1.170.41" - "@tanstack/router-plugin": "npm:^1.168.42" + "@tanstack/react-router-ssr-query": "npm:^1.167.3" + "@tanstack/react-start": "npm:^1.168.60" "@types/node": "npm:^26.6.4" "@types/react": "npm:^19.3.0" "@types/react-dom": "npm:^19.3.0" @@ -3485,8 +3871,10 @@ __metadata: clsx: "npm:^2.1.1" cmdk: "npm:^1.1.1" framer-motion: "npm:^12.42.2" + graphql: "npm:^17.0.2" hono: "npm:^4.13.12" lucide-react: "npm:^0.562.0" + nitro: "npm:3.0.260903-beta" radix-ui: "npm:^1.6.7" react: "npm:^19.3.0" react-dom: "npm:^19.3.0" @@ -3555,6 +3943,18 @@ __metadata: languageName: node linkType: hard +"vitefu@npm:^1.1.3": + version: 1.1.3 + resolution: "vitefu@npm:1.1.3" + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/6cfcd52a17339f63f71ef08787a68850a17a9d9dd7a688e29e41067f963eb2cd5192bb65d38df87fa3a733a21e0229aaa6382201655c1599699e5d6669e3e094 + languageName: node + linkType: hard + "webpack-virtual-modules@npm:^0.6.2": version: 0.6.2 resolution: "webpack-virtual-modules@npm:0.6.2" @@ -3573,6 +3973,18 @@ __metadata: languageName: node linkType: hard +"xmlbuilder2@npm:^4.0.3": + version: 4.0.3 + resolution: "xmlbuilder2@npm:4.0.3" + dependencies: + "@oozcitak/dom": "npm:^2.0.2" + "@oozcitak/infra": "npm:^2.0.2" + "@oozcitak/util": "npm:^10.0.0" + js-yaml: "npm:^4.1.1" + checksum: 10c0/ba257ca8a29b35d916fda190659b95d861137922fc792f760223f4a3bad07b5f18c992fb3a669d3dd6de6607eff64440cda86de083fc4648e4ec6b8f6be636a0 + languageName: node + linkType: hard + "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1"