Skip to content

Commit 8d47532

Browse files
committed
Improve Start runtime hero panel
1 parent 4db8cdf commit 8d47532

1 file changed

Lines changed: 134 additions & 89 deletions

File tree

src/components/landing/StartLanding.tsx

Lines changed: 134 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,46 @@ const featureCards = [
5757
},
5858
]
5959

60-
const routeContracts = [
60+
const startRuntimeSteps = [
6161
{
62+
label: 'Match route',
6263
file: 'routes/_app.projects.$id.tsx',
63-
tag: 'route',
64-
title: 'Types flow from the route file.',
65-
body: 'Path params, search schemas, route context, loader data, links, and navigate calls all stay tied to the generated route map.',
64+
body: 'Router narrows params, search, context, loader deps, and links before React starts rendering.',
6665
},
6766
{
68-
file: 'loader: ({ context, params }) => ...',
69-
tag: 'data',
70-
title: 'Data starts before render.',
71-
body: 'Loaders coordinate async work in parallel, preload on intent, use Router cache, or hydrate TanStack Query for client-side reuse.',
67+
label: 'Run loader',
68+
file: 'loader({ context, params })',
69+
body: 'Start can do the preload on the server, dehydrate Query, and reuse the same contract during client navigation.',
7270
},
7371
{
74-
file: 'createServerFn({ method: "POST" })',
75-
tag: 'server',
76-
title: 'The server boundary is deliberate.',
77-
body: 'Run database, environment, file-system, auth, or mutation work on the server while keeping the call typed and validated.',
72+
label: 'Call server function',
73+
file: 'createServerFn({ method: "GET" })',
74+
body: 'Database, auth, and environment work stay behind an explicit, validated server boundary.',
75+
},
76+
{
77+
label: 'Stream document',
78+
file: 'SSR + route data + pending UI',
79+
body: 'The HTML shell, head tags, loader data, and useful pending states can leave as one streaming response.',
80+
},
81+
{
82+
label: 'Ship runtime output',
83+
file: 'runtime adapter for the host',
84+
body: 'The same app model builds for the deployment target instead of changing how routes are authored.',
85+
},
86+
]
87+
88+
const startRuntimeOutputs = [
89+
{
90+
label: 'Route contract',
91+
value: 'params.id, search.tab, loaderData',
92+
},
93+
{
94+
label: 'Server boundary',
95+
value: 'db, auth, env, files, mutations',
96+
},
97+
{
98+
label: 'Deploy output',
99+
value: 'Node, Workers, Netlify, Railway',
78100
},
79101
]
80102

@@ -356,10 +378,6 @@ export default function StartLanding() {
356378
}
357379

358380
function StartRuntimePanel() {
359-
const [activeContractIndex, setActiveContractIndex] = React.useState(0)
360-
const activeContract =
361-
routeContracts[activeContractIndex] ?? routeContracts[0]
362-
363381
return (
364382
<div className="min-w-0 w-full max-w-full overflow-hidden rounded-lg border border-cyan-200 bg-white p-4 shadow-sm shadow-cyan-950/5 dark:border-cyan-900 dark:bg-zinc-950">
365383
<div className="flex items-center justify-between gap-3">
@@ -375,92 +393,119 @@ function StartRuntimePanel() {
375393

376394
<div className="mt-4">
377395
<p className="text-sm font-black leading-5 text-zinc-950 dark:text-white">
378-
Start begins where Router leaves off.
396+
Start keeps Router as the application contract.
379397
</p>
380398
<p className="mt-2 text-xs leading-5 text-zinc-600 dark:text-zinc-400">
381-
The route tree owns the application contract. Start adds execution,
382-
server functions, server routes, and build output around it without
383-
replacing that contract.
399+
Then it runs the server work Router shouldn't own: SSR, streaming,
400+
server functions, server routes, and deployable output.
384401
</p>
385402
</div>
386403

387-
<div className="mt-4 rounded-lg bg-cyan-50 p-3 dark:bg-cyan-950/30">
388-
<div className="flex min-w-0 items-start justify-between gap-3">
389-
<p className="min-w-0 truncate font-mono text-xs font-bold text-cyan-950 dark:text-cyan-100">
390-
{activeContract.file}
391-
</p>
392-
<span className="shrink-0 rounded-md bg-cyan-500 px-1.5 py-0.5 text-[0.65rem] font-black uppercase leading-none text-white">
393-
{activeContract.tag}
394-
</span>
404+
<div className="mt-4 grid gap-4 xl:grid-cols-[1.04fr_0.96fr]">
405+
<div className="min-w-0 overflow-hidden rounded-lg bg-zinc-950 p-4 text-zinc-100 shadow-inner shadow-black/20 dark:bg-black">
406+
<div className="flex min-w-0 items-center justify-between gap-3">
407+
<p className="min-w-0 truncate font-mono text-xs font-bold text-cyan-200">
408+
GET /projects/tanstack?tab=activity
409+
</p>
410+
<span className="shrink-0 rounded-md bg-emerald-400/15 px-1.5 py-0.5 text-[0.65rem] font-black uppercase leading-none text-emerald-200">
411+
server first
412+
</span>
413+
</div>
414+
415+
<div className="mt-4 min-w-0 overflow-x-auto pb-1 font-mono text-[0.72rem] leading-5">
416+
<p className="whitespace-nowrap">
417+
<span className="text-pink-300">export const</span>{' '}
418+
<span className="text-white">Route</span> = createFileRoute(
419+
<span className="text-emerald-300">'/_app/projects/$id'</span>)(
420+
{'{'}
421+
</p>
422+
<p className="whitespace-nowrap pl-4">
423+
validateSearch:{' '}
424+
<span className="text-emerald-300">projectSearchSchema</span>,
425+
</p>
426+
<p className="whitespace-nowrap pl-4">
427+
loader: ({'{'} context, params {'}'}) =&gt;
428+
</p>
429+
<p className="whitespace-nowrap pl-8">
430+
context.queryClient.ensureQueryData(projectQuery(params.id)),
431+
</p>
432+
<p className="whitespace-nowrap">{'})'}</p>
433+
<p className="mt-3 whitespace-nowrap text-zinc-400">
434+
<span className="text-pink-300">const</span>{' '}
435+
<span className="text-cyan-300">getProject</span> =
436+
createServerFn({'{'} method:{' '}
437+
<span className="text-emerald-300">'GET'</span> {'}'})
438+
</p>
439+
<p className="whitespace-nowrap pl-4 text-zinc-400">
440+
.handler(({'{'} data {'}'}) =&gt; db.project.find(data.id))
441+
</p>
442+
</div>
443+
</div>
444+
445+
<div className="rounded-lg bg-cyan-50 p-4 dark:bg-cyan-950/30">
446+
<div className="flex items-center justify-between gap-3">
447+
<p className="text-sm font-black text-cyan-950 dark:text-cyan-100">
448+
Request trace
449+
</p>
450+
<span className="inline-flex items-center gap-1.5 rounded-md bg-white px-1.5 py-0.5 text-[0.65rem] font-black uppercase text-cyan-800 dark:bg-cyan-950 dark:text-cyan-200">
451+
<span className="h-1.5 w-1.5 rounded-full bg-cyan-500 motion-safe:animate-pulse" />
452+
streaming
453+
</span>
454+
</div>
455+
456+
<div className="mt-4 grid gap-3">
457+
{startRuntimeSteps.map((step, index) => {
458+
const isLastStep = index === startRuntimeSteps.length - 1
459+
460+
return (
461+
<div
462+
key={step.label}
463+
className="grid grid-cols-[2rem_1fr] gap-3"
464+
>
465+
<span className="flex h-8 w-8 items-center justify-center rounded-lg border border-cyan-200 bg-white text-xs font-black text-cyan-800 dark:border-cyan-800 dark:bg-zinc-950 dark:text-cyan-200">
466+
{index + 1}
467+
</span>
468+
<div
469+
className={
470+
isLastStep
471+
? 'min-w-0'
472+
: 'min-w-0 border-b border-cyan-200/70 pb-3 dark:border-cyan-800/70'
473+
}
474+
>
475+
<div className="flex min-w-0 items-start justify-between gap-3">
476+
<p className="text-sm font-black leading-5 text-zinc-950 dark:text-white">
477+
{step.label}
478+
</p>
479+
<p className="min-w-0 truncate font-mono text-[0.65rem] font-bold text-cyan-800/80 dark:text-cyan-100/75">
480+
{step.file}
481+
</p>
482+
</div>
483+
<p className="mt-1 text-xs leading-5 text-cyan-950/75 dark:text-cyan-100/75">
484+
{step.body}
485+
</p>
486+
</div>
487+
</div>
488+
)
489+
})}
490+
</div>
395491
</div>
396-
<p className="mt-2 text-sm font-black leading-5 text-zinc-950 dark:text-white">
397-
{activeContract.title}
398-
</p>
399-
<p className="mt-1 text-xs leading-5 text-cyan-950/75 dark:text-cyan-100/75">
400-
{activeContract.body}
401-
</p>
402492
</div>
403493

404-
<div className="mt-4 space-y-2">
405-
{routeContracts.map((contract, index) => (
406-
<button
407-
key={contract.file}
408-
aria-pressed={activeContractIndex === index}
409-
className={
410-
activeContractIndex === index
411-
? 'w-full rounded-lg border border-cyan-500 bg-cyan-500 px-3 py-2.5 text-left text-white'
412-
: 'w-full rounded-lg border border-zinc-100 bg-zinc-50 px-3 py-2.5 text-left transition-colors hover:border-cyan-300 dark:border-zinc-800 dark:bg-zinc-900 dark:hover:border-cyan-800'
413-
}
414-
type="button"
415-
onClick={() => setActiveContractIndex(index)}
494+
<div className="mt-4 grid gap-3 sm:grid-cols-3">
495+
{startRuntimeOutputs.map((output) => (
496+
<div
497+
key={output.label}
498+
className="rounded-lg border border-zinc-100 bg-zinc-50 px-3 py-2.5 dark:border-zinc-800 dark:bg-zinc-900"
416499
>
417-
<div className="flex min-w-0 items-start justify-between gap-3">
418-
<p
419-
className={
420-
activeContractIndex === index
421-
? 'min-w-0 truncate font-mono text-xs font-bold text-white'
422-
: 'min-w-0 truncate font-mono text-xs font-bold text-zinc-900 dark:text-zinc-100'
423-
}
424-
>
425-
{contract.file}
426-
</p>
427-
<span
428-
className={
429-
activeContractIndex === index
430-
? 'shrink-0 rounded-md bg-white/20 px-1.5 py-0.5 text-[0.65rem] font-black uppercase leading-none text-white'
431-
: 'shrink-0 rounded-md bg-cyan-100 px-1.5 py-0.5 text-[0.65rem] font-black uppercase leading-none text-cyan-800 dark:bg-cyan-950 dark:text-cyan-200'
432-
}
433-
>
434-
{contract.tag}
435-
</span>
436-
</div>
437-
<p
438-
className={
439-
activeContractIndex === index
440-
? 'mt-1.5 text-sm font-black leading-5 text-white'
441-
: 'mt-1.5 text-sm font-black leading-5 text-zinc-950 dark:text-white'
442-
}
443-
>
444-
{contract.title}
500+
<p className="text-xs font-black text-zinc-950 dark:text-white">
501+
{output.label}
445502
</p>
446-
<p
447-
className={
448-
activeContractIndex === index
449-
? 'mt-1 text-xs leading-5 text-white/80'
450-
: 'mt-1 text-xs leading-5 text-zinc-600 dark:text-zinc-400'
451-
}
452-
>
453-
{contract.body}
503+
<p className="mt-1 text-xs leading-5 text-zinc-600 dark:text-zinc-400">
504+
{output.value}
454505
</p>
455-
</button>
506+
</div>
456507
))}
457508
</div>
458-
459-
<div className="mt-3 rounded-lg bg-cyan-50 px-3 py-2 text-xs leading-5 text-cyan-950 dark:bg-cyan-950/40 dark:text-cyan-100">
460-
<span className="font-black">Result:</span> a Router app first; SSR,
461-
streaming, APIs, middleware, and deployment added where the app needs
462-
them.
463-
</div>
464509
</div>
465510
)
466511
}

0 commit comments

Comments
 (0)