Skip to content
Merged
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Convex writes repo-root deployment configuration to `.env.local` during local se

The local Convex bootstrap now mirrors `CONVEX_URL` into `apps/web/.env.local` as `NEXT_PUBLIC_CONVEX_URL` so the web app can read the placeholder `health:status` query through the Convex client runtime. If you want the homepage to show live backend data instead of the local configuration fallback, run `pnpm bootstrap:backend:local` first and keep `pnpm dev:backend:local` running while you use `pnpm dev:web`.

The first server-side `Next.js -> Convex` baseline now lives at `/server-status`. It uses `fetchQuery` from `convex/nextjs` on a dedicated route rendered dynamically, while the homepage keeps the reactive client-side `useQuery` path.

`pnpm verify` is the full repo verification pass and now includes the local Convex bootstrap checks. If you are iterating on the web app only, use `pnpm verify:web` for the lighter web-only path.

## Start here
Expand Down
1 change: 1 addition & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ pnpm build:web
- language baseline: `TypeScript`
- styling baseline: `Tailwind CSS`
- the app mounts a Convex provider baseline and the homepage performs a live `health:status` query when `apps/web/.env.local` contains `NEXT_PUBLIC_CONVEX_URL`; the local Convex bootstrap now mirrors that value automatically from the repo-root Convex bootstrap output
- the server-side Convex baseline lives at `/server-status` and uses `fetchQuery` from a server component without replacing the reactive client pattern on `/`
- auth, billing, and deployment wiring still belong to follow-on issues
26 changes: 16 additions & 10 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Link from "next/link";
import { BackendStatusCard } from "./backend-status-card";

export default function Home() {
Expand Down Expand Up @@ -34,6 +35,12 @@ export default function Home() {
>
Next.js docs
</a>
<Link
className="inline-flex items-center justify-center rounded-full border border-border bg-surface-strong px-5 py-3 text-sm font-medium transition hover:-translate-y-0.5"
href="/server-status"
>
Server-side baseline
</Link>
<a
className="inline-flex items-center justify-center rounded-full border border-border bg-surface-strong px-5 py-3 text-sm font-medium transition hover:-translate-y-0.5"
href="https://www.convex.dev/"
Expand Down Expand Up @@ -68,7 +75,7 @@ export default function Home() {
</div>
<div className="flex items-start justify-between gap-4">
<dt className="text-muted">Next issues</dt>
<dd className="text-right font-medium">#64, #56, #59</dd>
<dd className="text-right font-medium">#9, #56, #59</dd>
</div>
</dl>

Expand All @@ -85,13 +92,12 @@ export default function Home() {
Now in place
</p>
<h2 className="mt-4 text-2xl font-semibold tracking-[-0.03em]">
A clean frontend baseline
Client and server baselines
</h2>
<p className="mt-3 text-sm leading-7 text-muted">
The repo now has a real web surface under{" "}
<code className="font-mono text-[0.95em]">apps/web</code>, ready for
local development, Convex integration, linting, and production
builds.
The repo now has one reactive client read and one explicit server-side
read pattern under <code className="font-mono text-[0.95em]">apps/web</code>,
ready to support later profile and auth work without mixing patterns.
</p>
</article>

Expand All @@ -114,12 +120,12 @@ export default function Home() {
Immediate follow-on
</p>
<h2 className="mt-4 text-2xl font-semibold tracking-[-0.03em]">
Add the first server-side data baseline
Define the profile data foundation
</h2>
<p className="mt-3 text-sm leading-7 text-muted">
The next meaningful milestone is documenting the first server-side
Convex read path so App Router usage stays clean as real features
land.
The next meaningful milestone is establishing the first durable
profile schema so public pages and claim flows can build on typed
domain records instead of the bootstrap health placeholder.
</p>
</article>
</section>
Expand Down
148 changes: 148 additions & 0 deletions apps/web/src/app/server-status/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import Link from "next/link";
import { fetchBackendStatus } from "@/convex/server";

export const dynamic = "force-dynamic";

export default async function ServerStatusPage() {
const status = await fetchBackendStatus();

return (
<main className="min-h-screen px-6 py-10 text-foreground sm:px-10 lg:px-16">
<div className="mx-auto flex w-full max-w-4xl flex-col gap-8">
<section className="overflow-hidden rounded-[2rem] border border-border bg-surface shadow-[0_24px_80px_rgba(64,40,24,0.12)] backdrop-blur">
<div className="flex flex-col gap-8 px-6 py-8 sm:px-8 lg:px-10 lg:py-10">
<div className="flex items-center gap-3 text-sm uppercase tracking-[0.28em] text-muted">
<span className="rounded-full border border-border px-3 py-1">VRDex</span>
<span>Server-side Convex baseline</span>
</div>

<div className="max-w-3xl space-y-5">
<h1 className="text-4xl leading-none font-semibold tracking-[-0.04em] sm:text-6xl">
First server-side App Router read path.
</h1>
<p className="max-w-2xl text-base leading-7 text-muted sm:text-lg">
This route demonstrates the baseline <code className="font-mono text-[0.95em]">Next.js</code>{" "}
server-component pattern for Convex in VRDex: use <code className="font-mono text-[0.95em]">fetchQuery</code>{" "}
for a server-only read, and keep <code className="font-mono text-[0.95em]">useQuery</code> for
reactive client surfaces.
</p>
</div>

<div className="flex flex-col gap-3 sm:flex-row">
<Link
className="inline-flex items-center justify-center rounded-full bg-accent px-5 py-3 text-sm font-medium text-white transition hover:bg-accent-strong"
href="/"
>
Back to homepage
</Link>
<a
className="inline-flex items-center justify-center rounded-full border border-border bg-surface-strong px-5 py-3 text-sm font-medium transition hover:-translate-y-0.5"
href="https://docs.convex.dev/client/nextjs/app-router/server-rendering"
target="_blank"
rel="noreferrer"
>
Convex server docs
</a>
</div>
</div>
</section>

<section className="grid gap-4 lg:grid-cols-[1.2fr_0.8fr]">
<article className="rounded-[1.5rem] border border-border bg-surface px-5 py-6">
<p className="font-mono text-xs uppercase tracking-[0.28em] text-muted">
Live result
</p>

{status.kind === "live" ? (
<>
<h2 className="mt-4 text-2xl font-semibold tracking-[-0.03em]">
Server read reached Convex
</h2>
<p className="mt-3 text-sm leading-7 text-muted">
This page rendered on the server using <code className="font-mono text-[0.95em]">fetchQuery(api.health.status)</code>{" "}
before the response reached the browser.
</p>

<dl className="mt-5 grid gap-3 text-sm sm:grid-cols-2">
<div className="rounded-2xl border border-border bg-surface-strong px-4 py-4">
<dt className="font-mono text-[11px] uppercase tracking-[0.24em] text-muted">
Status
</dt>
<dd className="mt-2 font-medium">{status.data.status}</dd>
</div>
<div className="rounded-2xl border border-border bg-surface-strong px-4 py-4">
<dt className="font-mono text-[11px] uppercase tracking-[0.24em] text-muted">
Scope
</dt>
<dd className="mt-2 font-medium">{status.data.scope}</dd>
</div>
<div className="rounded-2xl border border-border bg-surface-strong px-4 py-4">
<dt className="font-mono text-[11px] uppercase tracking-[0.24em] text-muted">
Backend
</dt>
<dd className="mt-2 font-medium">{status.data.backend}</dd>
</div>
<div className="rounded-2xl border border-border bg-surface-strong px-4 py-4">
<dt className="font-mono text-[11px] uppercase tracking-[0.24em] text-muted">
Note
</dt>
<dd className="mt-2 font-medium">{status.data.note}</dd>
</div>
</dl>
</>
) : status.kind === "missing-url" ? (
<>
<h2 className="mt-4 text-2xl font-semibold tracking-[-0.03em]">
Convex URL not configured
</h2>
<p className="mt-3 text-sm leading-7 text-muted">
Run <code className="font-mono text-[0.95em]">pnpm bootstrap:backend:local</code> so the local
bootstrap writes <code className="font-mono text-[0.95em]">NEXT_PUBLIC_CONVEX_URL</code> into
<code className="font-mono text-[0.95em]"> apps/web/.env.local</code> before using this
server-side route.
</p>
</>
) : (
<>
<h2 className="mt-4 text-2xl font-semibold tracking-[-0.03em]">
Convex server read failed
</h2>
<p className="mt-3 text-sm leading-7 text-muted">
Start <code className="font-mono text-[0.95em]">pnpm dev:backend:local</code>, confirm
<code className="font-mono text-[0.95em]"> NEXT_PUBLIC_CONVEX_URL</code> is available to the web app,
and reload this page.
</p>
</>
)}
</article>

<aside className="rounded-[1.5rem] border border-border bg-surface px-5 py-6">
<p className="font-mono text-xs uppercase tracking-[0.28em] text-muted">
Pattern rule
</p>
<dl className="mt-5 space-y-4 text-sm">
<div className="border-b border-border pb-4">
<dt className="font-medium">Use <code className="font-mono text-[0.95em]">fetchQuery</code></dt>
<dd className="mt-2 leading-6 text-muted">
For server components, route handlers, and server actions that only need a server-side read.
</dd>
</div>
<div className="border-b border-border pb-4">
<dt className="font-medium">Use <code className="font-mono text-[0.95em]">useQuery</code></dt>
<dd className="mt-2 leading-6 text-muted">
For reactive client components like the homepage runtime card that should update after first render.
</dd>
</div>
<div>
<dt className="font-medium">Defer <code className="font-mono text-[0.95em]">preloadQuery</code></dt>
<dd className="mt-2 leading-6 text-muted">
Until a feature actually needs server-rendered first paint plus a hydrated reactive client handoff.
</dd>
</div>
</dl>
</aside>
</section>
</div>
</main>
);
}
25 changes: 25 additions & 0 deletions apps/web/src/convex/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { fetchQuery } from "convex/nextjs";
import { api } from "@convex-generated-api";

export async function fetchBackendStatus() {
if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
return { kind: "missing-url" as const };
}

try {
const data = await fetchQuery(api.health.status, {});

return {
kind: "live" as const,
data,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);

console.error(`Server-side Convex fetchQuery failed: ${message}`);

return {
kind: "error" as const,
};
}
}
14 changes: 13 additions & 1 deletion docs/backend/convex-bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ It is intentionally narrow: enough structure to run Convex locally, generate typ
- `convex.json` pins Convex to Node `22` so local backend runtime expectations stay aligned with the repo's current Node baseline
- `convex/tsconfig.json` provides the TypeScript settings Convex uses to typecheck backend source files
- the web app consumes `health:status` as the first real `Next.js -> Convex` runtime path, mounting a client-side provider baseline and surfacing the result in the homepage without inventing early product schema
- the web app also exposes `/server-status`, where a server component uses `fetchQuery` against the same `health:status` function as the initial server-side baseline

## Local workflow

Expand All @@ -36,6 +37,7 @@ Notes:

- Convex writes deployment configuration to the repo-root `.env.local` file.
- The local Convex wrapper mirrors the repo-root `CONVEX_URL` into `apps/web/.env.local` as `NEXT_PUBLIC_CONVEX_URL` so the web app can follow the normal client-side Convex + Next.js convention without leaking a non-public variable through server props.
- That same `NEXT_PUBLIC_CONVEX_URL` value is also what `fetchQuery` uses for the current server-side baseline route, so local client and server reads share one deployment setting.
- Anonymous local backend state for this repo is kept under `.convex-home/` and `.convex-tmp/` so the bootstrap does not collide with other Convex projects on the same machine.
- The current bootstrap is local-development focused. Production deploy keys, preview deployments, and frontend environment wiring belong to follow-on issues.
- Committed files in `convex/_generated/` are treated as checked-in build artifacts and should remain diff-free after `pnpm check:backend:generated`.
Expand All @@ -51,5 +53,15 @@ Keep the initial backend slice simple:
## Follow-on issues

- `#55` wires the web app to the first Convex client/runtime path using `health:status`
- `#64` should add the first server-side `Next.js -> Convex` data path once the client runtime baseline is stable
- `#64` adds the first server-side `Next.js -> Convex` data path with `fetchQuery` on `/server-status`
- schema, auth, billing, and production deployment posture should land in their own issues instead of bloating the bootstrap

## App Router baseline

The current rule is intentionally narrow:

- use `useQuery` inside client components when the UI should stay reactive after first render
- use `fetchQuery` inside server components when the page only needs a server-side read
- defer `preloadQuery` until a real feature needs server-rendered first paint plus a hydrated reactive handoff

This keeps the first App Router pattern copyable without introducing multiple competing baselines before auth and domain work arrive.
1 change: 1 addition & 0 deletions docs/planning/engineering-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Current recommendation:
- keep the first backend slice schema-light with an explicit health query instead of guessing at product tables too early
- use local-development-friendly Convex setup first, then layer in frontend wiring, auth, billing, and production deployment posture through follow-on issues
- once the local backend bootstrap is deterministic, include it in the baseline PR verification pass alongside the web checks
- use one explicit server-side App Router baseline once client wiring is stable: `fetchQuery` for server-only reads, with `preloadQuery` deferred until a feature truly needs hydrated reactivity after server render

## Monetization direction

Expand Down
Loading