Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,23 +466,23 @@ Every export, with who uses it and why it exists. An export with no user in the
or ChessMerge goes; the list is checked at every release. At 0.8.0 and 0.5.0 that removed
`serveAsset` and `useSignedIn`.

| Export | Used by | Why |
| ------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| hosting `serveSite` | `convex/http.ts` of every app | The one entry: files, then `worker`, which is the Worker script; its absence is the files-only Worker |
| hosting `convex.config` | `convex/convex.config.ts` of every app | Convex's component definition, for `app.use` |
| hosting `_generated/component.js` (types) | The generated `api.d.ts` of every app | Convex's codegen imports it for the component's API type |
| CLI `deploy --build --dist [--dev \| --preview-name]` | App scripts and CI | The upload must run between the build and the push, with the deployment chosen by the same flags as Convex's CLI |
| CLI `--html-handling`, `--not-found-handling` | SPA example; ChessMerge (`none`) | Cloudflare's own asset settings, stored with the version |
| CLI `--component` | None today | Convex's own `app.use(hosting, { name })` option; the CLI must match it; Bristol's production used it while hosting 0.3 and 0.4 ran under two names |
| CLI `upload` | `deploy` itself, inside `npx convex deploy --cmd` | The step after the build has to be a program that a shell can call |
| adapter `vite`: `convexSsr()` | `vite.config.ts` of server-rendered apps | The `ssr` environment settings that make Start's server build run in Convex's runtime |
| adapter `server` | `src/server.ts` of server-rendered apps | Start's handler with React's Web Stream renderer, typed as TanStack's `RequestHandler<Register>`, so `fetch(request, { context })` takes the app's registered context |
| adapter `server-build` (types) | `convex/http.ts` of server-rendered apps | The type of the built server module: `{ fetch(request) }`, the shape of the app's `src/server.ts` |
| adapter `convex-auth`: `sessionBeforeLoad` | The root route | The same `beforeLoad` in every app with cookie sessions |
| adapter `convex-auth`: `SessionProvider` | The root component | Convex Auth's provider with the server token, plus the release of `expectAuth` for a signed-out visitor |
| adapter `convex-auth`: `redirectWithoutSession`, `RedirectWithoutSession` | The `_authed` layout | The server render has the cookie; the browser follows the provider; two moments, two pieces |
| adapter `convex-auth`: `SignedIn`, `SignedOut` | Bristol's header and menus | Convex's provider reports loading during the server render and the hydration; these wait for the answer |
| adapter `convex-auth`: `useSignOutSession` | The header's sign-out in the SSR example and Bristol | Convex Auth's `signOut()` resolves when the cookie route failed; this asks the route first, then clears the client, then reloads |
| Export | Used by | Why |
| ------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| hosting `serveSite` | `convex/http.ts` of every app | The one entry: files, then `worker`, which is the Worker script; its absence is the files-only Worker |
| hosting `convex.config` | `convex/convex.config.ts` of every app | Convex's component definition, for `app.use` |
| hosting `_generated/component.js` (types) | The generated `api.d.ts` of every app | Convex's codegen imports it for the component's API type |
| CLI `deploy --build --dist [--dev \| --preview-name]` | App scripts and CI | The upload must run between the build and the push, with the deployment chosen by the same flags as Convex's CLI |
| CLI `--html-handling`, `--not-found-handling` | SPA example; ChessMerge (`none`) | Cloudflare's own asset settings, stored with the version |
| CLI `--component` | None today | Convex's own `app.use(hosting, { name })` option; the CLI must match it; Bristol's production used it while hosting 0.3 and 0.4 ran under two names |
| CLI `upload` | `deploy` itself, inside `npx convex deploy --cmd` | The step after the build has to be a program that a shell can call |
| adapter `vite`: `convexSsr()` | `vite.config.ts` of server-rendered apps | The `ssr` environment settings that make Start's server build run in Convex's runtime, the `node:stream` stub included |
| adapter `server` | `src/server.ts` of server-rendered apps | Start's own stream handler, typed as TanStack's `RequestHandler<Register>`, so `fetch(request, { context })` takes the app's registered context; pages stream as on a Worker |
| adapter `server-build` (types) | `convex/http.ts` of server-rendered apps | The type of the built server module: `{ fetch(request) }`, the shape of the app's `src/server.ts` |
| adapter `convex-auth`: `sessionBeforeLoad` | The root route | The same `beforeLoad` in every app with cookie sessions |
| adapter `convex-auth`: `SessionProvider` | The root component | Convex Auth's provider with the server token, plus the release of `expectAuth` for a signed-out visitor |
| adapter `convex-auth`: `redirectWithoutSession`, `RedirectWithoutSession` | The `_authed` layout | The server render has the cookie; the browser follows the provider; two moments, two pieces |
| adapter `convex-auth`: `SignedIn`, `SignedOut` | Bristol's header and menus | Convex's provider reports loading during the server render and the hydration; these wait for the answer |
| adapter `convex-auth`: `useSignOutSession` | The header's sign-out in the SSR example and Bristol | Convex Auth's `signOut()` resolves when the cookie route failed; this asks the route first, then clears the client, then reloads |

## Repository

Expand Down
5 changes: 2 additions & 3 deletions examples/tanstack-ssr-workers.patch
Original file line number Diff line number Diff line change
Expand Up @@ -313,14 +313,13 @@ index c1fa9a2d4c1224a8cbda8132e633b7b066e137b4..3e6badb4024aed63c5e612137eb9fc19
tanstackStart({ pages: [{ path: "/about", prerender: { enabled: true, crawlLinks: false } }] }),
diff --git a/wrangler.jsonc b/wrangler.jsonc
new file mode 100644
index 0000000000000000000000000000000000000000..0514290649d7d95f8a16f528db32522d804ba834
index 0000000000000000000000000000000000000000..48c90a9e2b5789638c5baf349fe0cd88dcbbb282
--- /dev/null
+++ b/wrangler.jsonc
@@ -0,0 +1,9 @@
@@ -0,0 +1,8 @@
+{
+ "$schema": "node_modules/wrangler/config-schema.json",
+ "compatibility_date": "2026-10-01",
+ "compatibility_flags": ["nodejs_compat"],
+ "main": "src/server.ts",
+ "preview_urls": true,
+ // `wrangler preview`, the Workers Builds preview command, requires this block.
Expand Down
31 changes: 28 additions & 3 deletions examples/tanstack-ssr-workers/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as AuthedRouteImport } from './routes/_authed'
import { Route as AboutRouteImport } from './routes/about'
import { Route as SigninRouteImport } from './routes/signin'
import { Route as StreamRouteImport } from './routes/stream'
import { Route as AuthedTodosRouteImport } from './routes/_authed/todos'

const IndexRoute = IndexRouteImport.update({
Expand All @@ -34,6 +35,11 @@ const SigninRoute = SigninRouteImport.update({
path: '/signin',
getParentRoute: () => rootRouteImport,
} as any)
const StreamRoute = StreamRouteImport.update({
id: '/stream',
path: '/stream',
getParentRoute: () => rootRouteImport,
} as any)
const AuthedTodosRoute = AuthedTodosRouteImport.update({
id: '/todos',
path: '/todos',
Expand All @@ -44,12 +50,14 @@ export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/about': typeof AboutRoute
'/signin': typeof SigninRoute
'/stream': typeof StreamRoute
'/todos': typeof AuthedTodosRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/about': typeof AboutRoute
'/signin': typeof SigninRoute
'/stream': typeof StreamRoute
'/todos': typeof AuthedTodosRoute
}
export interface FileRoutesById {
Expand All @@ -58,21 +66,30 @@ export interface FileRoutesById {
'/_authed': typeof AuthedRouteWithChildren
'/about': typeof AboutRoute
'/signin': typeof SigninRoute
'/stream': typeof StreamRoute
'/_authed/todos': typeof AuthedTodosRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/about' | '/signin' | '/todos'
fullPaths: '/' | '/about' | '/signin' | '/stream' | '/todos'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/about' | '/signin' | '/todos'
id: '__root__' | '/' | '/_authed' | '/about' | '/signin' | '/_authed/todos'
to: '/' | '/about' | '/signin' | '/stream' | '/todos'
id:
| '__root__'
| '/'
| '/_authed'
| '/about'
| '/signin'
| '/stream'
| '/_authed/todos'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AuthedRoute: typeof AuthedRouteWithChildren
AboutRoute: typeof AboutRoute
SigninRoute: typeof SigninRoute
StreamRoute: typeof StreamRoute
}

declare module '@tanstack/react-router' {
Expand Down Expand Up @@ -105,6 +122,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SigninRouteImport
parentRoute: typeof rootRouteImport
}
'/stream': {
id: '/stream'
path: '/stream'
fullPath: '/stream'
preLoaderRoute: typeof StreamRouteImport
parentRoute: typeof rootRouteImport
}
'/_authed/todos': {
id: '/_authed/todos'
path: '/todos'
Expand All @@ -131,6 +155,7 @@ const rootRouteChildren: RootRouteChildren = {
AuthedRoute: AuthedRouteWithChildren,
AboutRoute: AboutRoute,
SigninRoute: SigninRoute,
StreamRoute: StreamRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
Expand Down
29 changes: 25 additions & 4 deletions examples/tanstack-ssr-workers/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useQuery, type QueryClient } from "@tanstack/react-query";
import { convexQuery, type ConvexQueryClient } from "@convex-dev/react-query";
import {
SessionProvider,
SignedIn,
SignedOut,
sessionBeforeLoad,
useSignOutSession,
} from "@samebase/convex-hosting-tanstack-start/convex-auth";
Expand Down Expand Up @@ -53,6 +55,10 @@ function Root() {
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/todos">Todos</Link>
{/* A document request: a navigation inside the app would run the loader in the browser. */}
<Link to="/stream" reloadDocument>
Stream
</Link>
</nav>
<SessionHeader />
</header>
Expand All @@ -65,13 +71,28 @@ function Root() {
}

function SessionHeader() {
// `useQuery` does not fetch on the server, so the header of a prerendered page stays empty until
// the browser has the answer.
// The server render knows the session from its token, so a rendered page carries the right
// header; a prerendered page has no token and shows its header once the browser knows.
return (
<>
<SignedOut>
<Link to="/signin">Sign in</Link>
</SignedOut>
<SignedIn>
<SignedInHeader />
</SignedIn>
</>
);
}

function SignedInHeader() {
// The root loader has queried the user for a page rendered as the user, so the name is in the
// cache; `useQuery` does not fetch on the server, so a prerendered page shows it when the browser
// has it.
const { data: me } = useQuery(convexQuery(api.users.me, {}));
const signOut = useSignOutSession("/auth/signout");
const [signOutFailed, setSignOutFailed] = useState(false);
if (me === undefined) return null;
if (me === null) return <Link to="/signin">Sign in</Link>;
if (!me) return null;
return (
<>
{`Signed in as ${me.name}`}
Expand Down
30 changes: 30 additions & 0 deletions examples/tanstack-ssr-workers/src/routes/stream.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Await, createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/stream")({
loader: () => ({
// A promise the loader does not await. A host that streams sends the page at once, with the
// fallback below, and the value when the promise resolves; a host that buffers sends the whole
// page when the promise has resolved. Two seconds, so the two parts are telling apart.
later: new Promise<string>((resolve) =>
setTimeout(() => resolve(new Date().toISOString()), 2000),
),
}),
component: Stream,
});

function Stream() {
const { later } = Route.useLoaderData();
return (
<main>
<h1>Streaming</h1>
<p>
The loader of this page returns a promise that resolves two seconds later, without awaiting
it. The server sends what it has and the value when it arrives, or the whole page at the
end, depending on the host. The value is the time the promise resolved on the server.
</p>
<Await promise={later} fallback={<p>Waiting for the value...</p>}>
{(value) => <p>Resolved at {value}.</p>}
</Await>
</main>
);
}
1 change: 0 additions & 1 deletion examples/tanstack-ssr-workers/wrangler.jsonc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"compatibility_date": "2026-10-01",
"compatibility_flags": ["nodejs_compat"],
"main": "src/server.ts",
"preview_urls": true,
// `wrangler preview`, the Workers Builds preview command, requires this block.
Expand Down
Loading
Loading