---
title: From a view to progressive HTML
---

# From a view to progressive HTML

Start with a component and an HTTP response. When part of the page becomes slow,
defer that component and send the surrounding HTML first.

## Set up JSX

Follow [Installation](/guide/getting-started/installation) and
[TypeScript setup](/guide/getting-started/typescript-setup) first.
The examples below live together in the same directory.

## Compose a view

Components are functions with TypeScript props. They can load their own data.

```tsx title="results.tsx"
// Replace this sample data source with your database or search service.
async function findResults() {
  return [{ title: "Getting started", href: "/guide/getting-started/installation" }];
}

export async function Results() {
  const items = await findResults();
  return (
    <ul>
      {items.map((item) => (
        <li>
          <a href={item.href}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}
```

## Return HTML from a route

Call this handler from your server's routing layer. It uses the standard
Response API; servers with their own response helpers can send the same HTML string.

```tsx
import { renderToString } from "@vincle/core";
import { Results } from "./results";

export async function GET() {
  const html = await renderToString(
    <main>
      <h1>Search results</h1>
      <Results />
    </main>,
  );

  return new Response(html, {
    headers: { "Content-Type": "text/html; charset=utf-8" },
  });
}
```

This response contains an HTML fragment. Wrap the view in your document layout
when serving a complete page.

The response waits for the complete tree. Async components execute in document
order; making a component async does not run its siblings in parallel.

## Let the results arrive later

Install the optional streaming package:

```sh
npm install @vincle/flow
```

Keep Results unchanged and wrap it in Defer. Use renderToStream instead of
renderToString to send the surrounding markup and fallback before the results.

```tsx
import { Defer, renderToStream } from "@vincle/flow";
import { NativeAdapter } from "@vincle/flow/adapters";
import { Results } from "./results";

function Page() {
  return (
    <main>
      <h1>Search results</h1>
      <Defer fallback={<p>Loading results…</p>}>
        <Results />
      </Defer>
    </main>
  );
}

const stream = renderToStream(() => <Page />, NativeAdapter);
```

The stream contains strings. In a server that accepts Web Streams, return it as a
byte stream from your route:

```tsx
return new Response(stream.pipeThrough(new TextEncoderStream()), {
  headers: { "Content-Type": "text/html; charset=utf-8" },
});
```

Your server and proxy must pass chunks through without buffering the whole
response.

The Native adapter supplies the browser patching script. If your application
already uses HTMX or Turbo, choose the corresponding
[adapter](/integration/adapters) and load its client library.

To cancel a deferred component's work, use a function child: it receives an
AbortSignal to forward to `fetch`. See [Streaming](/integration/streaming) for
cancellation and timeouts.

## Continue from here

- [Streaming](/integration/streaming): fragment delivery
- [HTTP serving](/integration/http-serving): response helpers and request negotiation
- [Adapters](/integration/adapters): choose how fragments reach the page
- [Security](/guide/security): escaping rules and trusted HTML
