From 1254976401b0693063853c2d773d9504b184758e Mon Sep 17 00:00:00 2001 From: Brenley Dueck Date: Tue, 3 Nov 2026 13:21:08 -0600 Subject: [PATCH] docs: unslop pass over Learn pages and tutorials --- src/routes/(0)index.mdx | 2 +- .../(1)getting-started/(0)quick-start.mdx | 5 ++- src/routes/(2)concepts/(0)reactivity.mdx | 15 ++++---- .../(2)concepts/(1)components-and-jsx.mdx | 10 +++--- src/routes/(2)concepts/(2)stores.mdx | 3 +- .../(2)concepts/(3)async-reactivity.mdx | 35 +++++++------------ src/routes/(2)concepts/(4)boundaries.mdx | 5 ++- src/routes/(2)concepts/(5)mutations.mdx | 4 +-- .../(2)concepts/(6)rendering-and-ssr.mdx | 5 ++- .../(3)building-apps/(0)app-structure.mdx | 8 ++--- .../(1)styling-and-assets.mdx | 5 ++- .../(3)building-apps/(2)head-and-metadata.mdx | 15 +++----- .../(1)reads-and-live-data.mdx | 3 +- .../(4)metadata-and-transport.mdx | 4 +-- .../(5)progressive-enhancement.mdx | 2 +- .../(6)streaming-ai-responses.mdx | 8 ++--- .../(7)durable-generation.mdx | 17 +++++---- .../(3)building-apps/(4)sessions-and-auth.mdx | 7 ++-- .../(3)building-apps/(5)environment.mdx | 5 --- .../(6)middleware-and-api-routes.mdx | 3 +- src/routes/(3)building-apps/(7)deployment.mdx | 9 ++--- .../(1)solid-router/(3)nested-routes.mdx | 4 +-- .../(1)solid-router/(4)navigation.mdx | 2 +- .../(4)routing/(1)solid-router/(5)data.mdx | 4 +-- .../(1)solid-router/(6)server-rendering.mdx | 5 +-- .../(4)routing/(2)tanstack/(0)index.mdx | 11 +++--- .../(4)routing/(3)integrate-a-router.mdx | 4 +-- src/routes/(5)guides/(0)thinking-in-solid.mdx | 2 +- .../(1)avoid-unnecessary-effects.mdx | 5 ++- .../(5)guides/(10)debugging-reactivity.mdx | 4 +-- src/routes/(5)guides/(11)performance.mdx | 2 +- .../(13)integrate-non-solid-code.mdx | 3 +- src/routes/(5)guides/(14)testing.mdx | 2 +- src/routes/(5)guides/(15)observability.mdx | 4 +-- .../(5)guides/(16)observability-adapters.mdx | 8 ++--- src/routes/(5)guides/(17)ai-tools.mdx | 4 +-- src/routes/(5)guides/(18)ai-sdk-chat.mdx | 5 ++- src/routes/(5)guides/(2)custom-primitives.mdx | 4 +-- src/routes/(5)guides/(3)state-management.mdx | 12 +++---- src/routes/(5)guides/(4)forms.mdx | 7 ++-- src/routes/(5)guides/(5)lists.mdx | 14 ++++---- .../(5)guides/(6)data-fetching-patterns.mdx | 10 +++--- src/routes/(5)guides/(7)protected-routes.mdx | 11 +++--- src/routes/(5)guides/(8)ssr-safe-code.mdx | 6 ++-- .../(5)guides/(9)choose-a-rendering-mode.mdx | 2 +- src/routes/(6)migration/(0)from-solid-1.mdx | 2 +- .../(1)data-fetching-from-solid-1.mdx | 15 ++++---- .../(6)migration/(3)from-solid-router.mdx | 2 +- .../(6)migration/(4)from-solid-meta.mdx | 21 +++++------ src/routes/(6)migration/(5)from-react.mdx | 14 ++++---- src/routes/(7)glossary.mdx | 6 ++-- src/tutorials/async-memos.mdx | 4 +-- src/tutorials/batching.mdx | 4 +-- src/tutorials/components.mdx | 2 +- src/tutorials/context.mdx | 2 +- src/tutorials/derived-values.mdx | 2 +- src/tutorials/directives.mdx | 2 +- src/tutorials/dynamic.mdx | 2 +- src/tutorials/effects.mdx | 2 +- src/tutorials/events.mdx | 2 +- src/tutorials/loading.mdx | 6 ++-- src/tutorials/optimistic-state.mdx | 2 +- src/tutorials/optimistic-stores.mdx | 2 +- src/tutorials/pending.mdx | 4 +-- src/tutorials/props.mdx | 4 +-- src/tutorials/streams.mdx | 2 +- src/tutorials/until.mdx | 2 +- 67 files changed, 179 insertions(+), 230 deletions(-) diff --git a/src/routes/(0)index.mdx b/src/routes/(0)index.mdx index 8b3bc515a..9cd3dbb56 100644 --- a/src/routes/(0)index.mdx +++ b/src/routes/(0)index.mdx @@ -45,7 +45,7 @@ Read them in this order the first time through: 3. [Reactivity](/concepts/reactivity): what a signal is, why reads have to happen in the right place, and when updates land. 4. [Components and JSX](/concepts/components-and-jsx): props, events, refs, lists, and conditional content. 5. [Stores](/concepts/stores): objects and arrays whose properties update independently, and how to update them through a draft. -6. [Avoid unnecessary effects](/guides/avoid-unnecessary-effects): the habit that separates working Solid code from fragile Solid code. +6. [Avoid unnecessary effects](/guides/avoid-unnecessary-effects): how to replace an effect that copies one value into another with a derivation. 7. [Async reactivity](/concepts/async-reactivity) and [Boundaries](/concepts/boundaries): loading data, showing fallbacks, and keeping the current screen visible while new data arrives. 8. [Mutations](/concepts/mutations): writing to a server with actions and optimistic stores, without changing the components that render the data. 9. [State management](/guides/state-management): where each piece of state lives, from a modal flag to the signed-in user. diff --git a/src/routes/(1)getting-started/(0)quick-start.mdx b/src/routes/(1)getting-started/(0)quick-start.mdx index d4f71bea6..3d53fec66 100644 --- a/src/routes/(1)getting-started/(0)quick-start.mdx +++ b/src/routes/(1)getting-started/(0)quick-start.mdx @@ -98,7 +98,7 @@ export default function App() { } ``` -Three things in this file come up again and again in Solid: +These parts of the file come up again throughout Solid: - `props.children` is where the matched page renders. Layouts in Solid are ordinary components that render their children. @@ -200,7 +200,6 @@ The button no longer updates, and the browser console shows a warning: Move it into a tracking scope (JSX, a memo, or an effect's compute function). ``` -This is the one rule that makes the rest of Solid make sense. A component body runs once, so a value read there is a snapshot. A read inside JSX, a memo, or an effect's compute function is tracked, and tracked reads update. The [Reactivity](/concepts/reactivity) page explains why, and what to do when you need a value in the component body anyway. @@ -282,7 +281,7 @@ describe("", () => { Run it with `npm test`. If you kept the step input, `getByRole("button")` still finds the only button. -The `flush()` call matters: Solid applies DOM updates in a batch after the event handler returns, so a test that asserts immediately after `fireEvent.click` sees the old text. +The test needs the `flush()` call because Solid applies DOM updates in a batch after the event handler returns, so a test that asserts immediately after `fireEvent.click` sees the old text. The [Testing guide](/guides/testing) covers this and the other environments. Run `npm run build`. diff --git a/src/routes/(2)concepts/(0)reactivity.mdx b/src/routes/(2)concepts/(0)reactivity.mdx index 7a5188a87..4ba6177a2 100644 --- a/src/routes/(2)concepts/(0)reactivity.mdx +++ b/src/routes/(2)concepts/(0)reactivity.mdx @@ -8,9 +8,7 @@ In the [Quick start](/getting-started/quick-start) you saw two versions of the s One read `count()` inside the JSX and updated on every click. The other read `count()` in the component body, and the button froze at zero while the console warned that the read "will not update". -This page explains why. -Solid does not re-run components. -It runs each component once, records which reactive values each expression reads, and re-runs only those expressions when the values they read change. +The reason is that Solid runs each component once, records which reactive values each expression reads, and re-runs only those expressions when the values they read change. The place where you read a value decides whether Solid can see the read. The examples use a small shopping cart: a line item with a price and a quantity, a subtotal, and a shipping estimate. @@ -53,7 +51,7 @@ A read inside a tracking scope subscribes that scope to the signal. A read anywhere else returns the current value and subscribes nothing. The component body is not a tracking scope. -That is deliberate: the body runs once to set up the component, and Solid must not re-run it, because that would recreate every signal and child inside it. +The body runs once to set up the component, and Solid must not re-run it, because that would recreate every signal and child inside it. So this version reads the quantity once and never again: ```tsx @@ -70,8 +68,7 @@ In development Solid warns: Move it into a tracking scope (JSX, a memo, or an effect's compute function). ``` -The fix is to move the read, not the value. -Wrap the calculation in a function and call the function from the JSX: +To fix it, wrap the calculation in a function and call the function from the JSX: ```tsx const subtotal = () => quantity() * price; @@ -122,7 +119,7 @@ function createSignal(value: T) { ``` `read` can only record a subscriber if a tracking scope is running at the moment it is called. -A read in the component body runs when `currentScope` is empty, so it returns the value and records nothing; that is the whole reason the place you read matters. +A read in the component body runs when `currentScope` is empty, so it returns the value and records nothing. ::: ## Derived values @@ -150,7 +147,7 @@ const total = createMemo(() => subtotal() * (1 - discount())); When `quantity` changes, `subtotal` recomputes. `total` recomputes because it read `subtotal()`. When the user types `SAVE1` on the way to `SAVE10`, `discount` recomputes and produces `0` again; because the result is equal to the last one, `total` is not notified. -That equality check is what makes a memo a useful boundary in a chain of derivations. +That equality check lets a memo stop an update partway through a chain of derivations. A memo's function must not write to signals or stores. It runs during an update, and a write inside it throws `[REACTIVE_WRITE_IN_OWNED_SCOPE]` in development. @@ -245,7 +242,7 @@ The guide [Avoid unnecessary effects](/guides/avoid-unnecessary-effects) works t Every memo, effect, and cleanup belongs to an owner. A component is an owner. When Solid removes a component from the page, it disposes everything the component created: subscriptions are dropped, cleanups run, and the memos and effects stop. -This is why a component can create an effect and never think about tearing it down. +This is why a component can create an effect without tearing it down by hand. The owner is whatever is running when the primitive is created. diff --git a/src/routes/(2)concepts/(1)components-and-jsx.mdx b/src/routes/(2)concepts/(1)components-and-jsx.mdx index c6861a84d..1c67e85fc 100644 --- a/src/routes/(2)concepts/(1)components-and-jsx.mdx +++ b/src/routes/(2)concepts/(1)components-and-jsx.mdx @@ -6,9 +6,9 @@ description: "Components as functions that run once: props, children, control fl A Solid component is a function that runs once. It sets up state, returns JSX, and is not called again for the life of the component. -Every rule below about props, children and control flow follows from that: the function body will not run a second time. +Every rule below about props, children and control flow follows from that. [Reactivity](/concepts/reactivity) explains how the JSX keeps updating after the function has returned. -This page covers props, events, refs, lists and conditions, and composition, continuing the shopping cart from that page. +The examples continue the shopping cart from that page. ## How JSX executes @@ -71,15 +71,13 @@ function Cart() { } ``` -`quantity={quantity()}` looks like it reads the signal once, when `Cart` runs. -It does not. -The compiler turns a dynamic attribute into a getter on the props object, so the read of `quantity()` happens when `LineItem` reads `props.quantity`, inside its JSX, in a tracking scope. +`quantity={quantity()}` looks like it reads the signal once, when `Cart` runs, but the compiler turns a dynamic attribute into a getter on the props object, so the read of `quantity()` happens when `LineItem` reads `props.quantity`, inside its JSX, in a tracking scope. That is why `props.quantity` in the child updates when the parent's signal changes, even though neither function runs again. This works only as long as the read stays on the props object. :::pitfall[Destructuring props reads them once] -The shortcut that works on a plain object is the one that breaks here: +Destructuring works on a plain object but not on props: ```tsx // Avoid: both read props.quantity in the component body, once diff --git a/src/routes/(2)concepts/(2)stores.mdx b/src/routes/(2)concepts/(2)stores.mdx index d96146234..c7340e74b 100644 --- a/src/routes/(2)concepts/(2)stores.mdx +++ b/src/routes/(2)concepts/(2)stores.mdx @@ -102,8 +102,7 @@ setCart((draft) => { Three properties changed, and three subscriptions are notified: the coupon reader, the Mug quantity input, and the `length` reader. The T-shirt row is not touched. -The habit from immutable state is to rebuild the collection. -That habit undoes what the store gives you: +The habit from immutable state is to rebuild the collection, which replaces the changed item with a new object: ```ts // Avoid: a new object for the changed item, so For rebuilds that row diff --git a/src/routes/(2)concepts/(3)async-reactivity.mdx b/src/routes/(2)concepts/(3)async-reactivity.mdx index 52941439f..a9555cbc1 100644 --- a/src/routes/(2)concepts/(3)async-reactivity.mdx +++ b/src/routes/(2)concepts/(3)async-reactivity.mdx @@ -12,7 +12,6 @@ Once a memo is async, a write to any input it depends on is held until the memo The page never shows a mix of old and new data. If you come from Solid 1 or from a pattern where every panel fetched and showed its own spinner on its own schedule, this is the part that will surprise you; the [in-flight work](#settled-view-and-in-flight-work) section shows it and the choices it gives you. -This page explains what async computations buy you and what you still have to decide. The examples continue the shopping cart from the [Reactivity](/concepts/reactivity) page. ## A memo that returns a promise @@ -42,8 +41,7 @@ function ProductDetail(props: { id: string }) { } ``` -`product()` has type `Product`, not `Product | undefined` and not `Promise`. -There is no loading flag to check and no optional chaining. +`product()` has type `Product`, not `Product | undefined` or `Promise`, so the JSX needs no loading flag and no optional chaining. If the expression is running, the value is there. Until the first result arrives, a read of `product()` reports that the value is not ready, and the nearest [`Loading`](/reference/solid-js/components-jsx/loading) boundary renders its fallback in place of the content. @@ -79,8 +77,7 @@ function ProductLayout(props: { product: Product }) { } ``` -`product={product()}` looks like it calls the accessor before the data exists. -It does not; the expression is evaluated when `ProductDetail` reads `props.product.name`. +`product={product()}` looks like it calls the accessor before the data exists, but the expression is evaluated when `ProductDetail` reads `props.product.name`. So `ProductLayout` renders immediately, `CategoryNav` renders immediately, and only the detail pane waits. Derived values behave the same way: @@ -103,7 +100,7 @@ No `await` and no promise type appear anywhere between the fetch and the JSX. ## Fetch high, block low -Because passing a value costs nothing, the two decisions that usually fight each other come apart: +Because passing a value costs nothing, where to fetch and where to block become separate decisions: - Where to create the async value is a performance decision. Higher in the tree means the request starts earlier and can run in parallel with other work. @@ -178,7 +175,7 @@ const brand = createMemo(() => fetchBrand(product().brandId)); ``` `brand` cannot start until `product` resolves, because the id comes from the response. -That is sequential because the data is sequential, and the dependency is visible in the code. +That dependency is visible in the code. Development builds can flag long chains like this with the `ASYNC_WATERFALL` diagnostic when attribution is enabled; see [Debugging reactivity](/guides/debugging-reactivity). ## Settled view and in-flight work @@ -198,9 +195,6 @@ The boundary controls what renders when no settled answer exists; it does not cr ### What the hold means for a shared input -The hold is simple to reason about for one memo and one view. -It is worth seeing for several. - A dashboard has a period selector and three panels. Each panel fetches its own data from the selected period, and the fetches take 200 milliseconds, one second, and ten seconds: @@ -231,13 +225,12 @@ For those ten seconds the page shows the new period's summary beside the old per In Solid the write to `period` is held. The selector keeps showing the old period, every panel keeps its old content, and at ten seconds the selector and all three panels change together. -Nothing on the page ever disagrees about which period it is showing. The cost is that the fast panel waits for the slow one, and if nothing on the page reacts to the click, the page looks dead for ten seconds. Neither behavior is right for every screen. A detail page, a form, or a set of totals that must add up should change together. A set of independent widgets should not have to. -The question to ask is about the update, not about any one request, and it is one your designer can answer: +The question covers the whole update, and a designer can answer it: **While this update is in flight, what should the user see?** @@ -248,7 +241,7 @@ The question to ask is about the update, not about any one request, and it is on | A placeholder in place of the content for the changed subject | [``](#show-a-placeholder-again-loading-on) around that content. | The first two rows are the same screen: the tab highlight moves at once, the content stays and dims, the content swaps when it is ready. -That is the standard interaction for a held update, and the rest of this section shows the pieces. +That is the standard interaction for a held update. The third row is for content that should not stay on screen when its subject changes, such as a panel for a different account. The hold applies only to updates. @@ -311,8 +304,7 @@ function Search() { Typing changes `query`, `results` starts a new request, and the previous results stay on screen with the `stale` class until the new ones land. -`isPending` is a question, not a piece of state. -It can be asked about the async source, about a memo derived from it, about a prop several components down, or about the signal whose write started the update: +`isPending` can be asked about the async source, about a memo derived from it, about a prop several components down, or about the signal whose write started the update: ```tsx function App() { @@ -332,7 +324,7 @@ function App() { Here the fetch lives inside `ProductPage`. `App` knows nothing about it, but `isPending(selectedId)` is `true` from the moment the write happens until everything downstream has settled, because the write is what is being held. -Notice that the list highlight does not move on click either. +The list highlight does not move on click either. `selectedId()` holds its old value like everything else, so the highlighted row always matches the content on screen. When the design wants the highlight to move immediately, read the input with [`latest`](#show-the-input-now-latest), covered next. @@ -383,7 +375,6 @@ Before any value exists, the read still follows the normal `Loading` path. Read the two sides together: `latest` on the control, the plain read on the content, `isPending` between them to dim the content. The highlight moves at once, the old product stays visible and dims, and the new product replaces it when its data is ready. -This is the everyday shape of a held update, not a special case. `latest` belongs on inputs. Reading data through `latest` shows a result before the rest of the update has agreed on it, so a panel can show the new period's numbers while the selector still shows the old period. @@ -403,9 +394,9 @@ The `on` prop is a dependency list: the boundary tracks the expression, and a ch ``` -A `Loading` boundary is the update's chance to finish without waiting for that content: the region shows its fallback and waits on its own. -Normally the update takes that chance only on the region's first load. -`on` extends the offer to later updates: when `accountId()` changes and the new account is not ready, the boundary gives up its old content and takes the wait back from the update. +A `Loading` boundary lets an update finish without waiting for the content inside it: the region shows its fallback and waits on its own. +Without `on`, that happens only on the region's first load. +With `on`, it also happens on later updates: when `accountId()` changes and the new account is not ready, the boundary drops its old content, and the update stops waiting on that region. If the panel was the only thing waiting, the update finishes at once. Controls outside the boundary see the new id, the panel shows its skeleton, and the content arrives when the account does. Pending work caused by anything `on` does not read, such as a refresh of the same account, leaves the content in place as usual. @@ -432,12 +423,12 @@ When the very same data is also read outside the boundary, the frame waits on th ``` In the `Avoid` version, picking another account looks like nothing happened until the account loads, then the heading and the details change together; the `on` had no visible effect because the heading kept the frame waiting on the data the boundary was about to show a skeleton for. -Development reports this as `LOADING_ON_OUTSIDE_HOLD`, naming the source, and the fix is structural: move the outside read under the boundary so one hold owns the data. +Development reports this as `LOADING_ON_OUTSIDE_HOLD`, naming the source, and the fix is to move the outside read under the boundary so one hold owns the data. The `Prefer` version is that fix. So the rule for `on` is that the boundary encloses every reader of the data it waits on. Other content waiting on the same input through its own data is fine; the skeleton arrives with the frame that content lands in. -That leaves a design choice rather than a rule about where boundaries go: +That leaves a design choice about where boundaries go: - One boundary with `on` around every reader of the subject. The whole subject shows one skeleton and appears together. diff --git a/src/routes/(2)concepts/(4)boundaries.mdx b/src/routes/(2)concepts/(4)boundaries.mdx index 8a817155d..db85b1855 100644 --- a/src/routes/(2)concepts/(4)boundaries.mdx +++ b/src/routes/(2)concepts/(4)boundaries.mdx @@ -8,7 +8,6 @@ With one boundary around a whole product page, a slow recommendations query keep With no boundary at all, an unhandled error halts the reactive system and nothing on the page updates again. Boundaries decide how much of the page a pending or failed read affects. [`Loading`](/reference/solid-js/components-jsx/loading) renders a fallback while async reads in its subtree have no value, [`Errored`](/reference/solid-js/components-jsx/errored) renders a fallback when its subtree throws, and [`Reveal`](/reference/solid-js/components-jsx/reveal) decides the order in which sibling regions appear. -Where you put each one is the design decision this page is about. ```tsx import { Errored, Loading, createMemo, createSignal } from "solid-js"; @@ -50,7 +49,7 @@ On first render the panel shows "Loading product…" and the picker is usable. When the request resolves the product appears; when it rejects, the error message and a **Retry** button appear in the same place. Pick another product and the panel shows the loading text again while the picker keeps working. -Three rules explain that behavior. +That behavior follows from these rules. A boundary handles status produced by reads in its own subtree, and the nearest matching boundary handles it. Loading and error status are separate, so `Loading` does not hide an error and `Errored` does not replace loading UI; both can protect the same region. Controls outside the boundary are outside its fallback, so the picker stays on screen in every state. @@ -92,7 +91,7 @@ Pending work caused by anything `on` does not read, such as a refresh of the sam The fallback appears with the same frame as the change that caused it. When nothing else is waiting on that change, that is at once. When other content outside the boundary is waiting on the same change, the page swaps when that content is ready, and the boundary shows its skeleton from that swap if its own content is still loading. -When the very same data is also read outside the boundary, the frame waits on it and the fallback can never appear; development warns `LOADING_ON_OUTSIDE_HOLD`, and the fix is structural: move the outside read under the boundary so one hold owns the data. +When the very same data is also read outside the boundary, the frame waits on it and the fallback can never appear; development warns `LOADING_ON_OUTSIDE_HOLD`, and the fix is to move the outside read under the boundary so one hold owns the data. The [Async reactivity](/concepts/async-reactivity#show-a-placeholder-again-loading-on) page shows each case. :::pitfall[Passing the accessor to on instead of reading it] diff --git a/src/routes/(2)concepts/(5)mutations.mdx b/src/routes/(2)concepts/(5)mutations.mdx index fb48b0cd0..acc389442 100644 --- a/src/routes/(2)concepts/(5)mutations.mdx +++ b/src/routes/(2)concepts/(5)mutations.mdx @@ -102,7 +102,7 @@ Solid discards the overlay when the action settles, whether the action succeeded If `api.add` throws, the overlay is dropped and the list is back where it started; there is no rollback code to write. If the server agrees with the prediction, reconciliation finds no differences and nothing updates. -Because actions ride the same hold as reads, the list never shows half a mutation, and quick repeated clicks do not interleave into a corrupted list. +Because actions use the same hold as reads, the list never shows half a mutation, and quick repeated clicks do not interleave into a corrupted list. :::pitfall[An await inside an action leaves the transaction] JavaScript has no way to keep the action's context across a plain `await`, which is why the body is a generator. @@ -345,7 +345,7 @@ Use `createOptimisticStore` for state that should revert when the request settle An action is not tied to the component that created it, so navigating away does not cancel it and the save still lands, like a form submit. That is the safe default: the action is often what navigates (save, then redirect), and cancelling it there would undo the write the user asked for. Its late writes are harmless, because nothing that was removed still reads them. -Guard the steps after a `yield` that only make sense on the old page, for example by comparing against the current record id, and pass an `AbortSignal` to the request when it should really stop. +Guard the steps after a `yield` that only make sense on the old page, for example by comparing against the current record id, and pass an `AbortSignal` to the request when it should stop. Do not retire the action from a component's `onCleanup`; see [Stop the work a run started](/concepts/async-reactivity#stop-the-work-a-run-started). ### A failed action does not show the `Errored` fallback diff --git a/src/routes/(2)concepts/(6)rendering-and-ssr.mdx b/src/routes/(2)concepts/(6)rendering-and-ssr.mdx index bf4678f09..878671cf6 100644 --- a/src/routes/(2)concepts/(6)rendering-and-ssr.mdx +++ b/src/routes/(2)concepts/(6)rendering-and-ssr.mdx @@ -15,9 +15,8 @@ The component was fine in the browser, but it now also runs in Node, where there Solid uses one component source for client rendering and server-side rendering (SSR). The JSX build target selects DOM operations for the browser or HTML-producing operations for the server. -This page covers what that asks of your code and which rendering function fits which situation. -Most applications need two things from this page: the [server and client boundaries](#server-and-client-boundaries) rules for browser-only code, and the rule in [streaming rendering](#streaming-rendering) about where `Loading` boundaries go. +Most applications need the [server and client boundaries](#server-and-client-boundaries) rules for browser-only code and the rule in [streaming rendering](#streaming-rendering) about where `Loading` boundaries go. In a project made by the CLI, the generated entries already call `render`, `hydrate`, and `renderToStream`; the sections on those functions are for code that calls the renderer directly. ## Server and client boundaries @@ -167,7 +166,7 @@ It computes again only when one of its inputs changes on the client or it is ref The memo still has to learn what it depends on, so the client runs its compute function once and discards the result. For that synchronous run, the global `Promise` and `fetch` are replaced by stand-ins whose promises never settle, so no request goes out. That makes the run harmless as long as the compute keeps nothing it creates. -A cache that outlives the run is the case that breaks: +A cache that outlives the run stores one of those stand-in promises: ```ts // Avoid: the promise stored while hydrating is a stand-in and never settles diff --git a/src/routes/(3)building-apps/(0)app-structure.mdx b/src/routes/(3)building-apps/(0)app-structure.mdx index b4798e1f2..df706b0c1 100644 --- a/src/routes/(3)building-apps/(0)app-structure.mdx +++ b/src/routes/(3)building-apps/(0)app-structure.mdx @@ -10,8 +10,8 @@ What is there is `src/App.tsx`, `src/Document.tsx`, and a `vite.config.ts` with `App` is the component every page renders inside; `Document` is the HTML shell around it. The plugin's start mode generates the rest: the server entry that renders ``, the client entry that mounts or hydrates it, and the request handler that serves both. -Most apps need only `App.tsx` and `Document.tsx`, and the first two sections below are about those files. -The sections on generated entries and `start.serverRenderSetup` are for projects that need to change the defaults, such as an authored server entry or a router that must load before the render starts. +Most apps need only `App.tsx` and `Document.tsx`. +Generated entries and `start.serverRenderSetup` are for projects that need to change the defaults, such as an authored server entry or a router that must load before the render starts. ## Three rendering modes, one layout @@ -108,7 +108,7 @@ export default function Document(props: ParentProps) { View the page source and this is the markup around the app. The plugin injects the client entry `