---
title: First render
---

# First render

Everything you need to render your first JSX to an HTML string.

## Hello world

`renderToString` always returns a `Promise<string>`, even for synchronous
trees. The string is ready to send over HTTP or write to a file.

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

const html = await renderToString(
  <html lang="en">
    <head>
      <title>Hello, world!</title>
    </head>
    <body>
      <h1>Hello, world!</h1>
    </body>
  </html>,
);
```

```html
<html lang="en">
  <head>
    <title>Hello, world!</title>
  </head>
  <body>
    <h1>Hello, world!</h1>
  </body>
</html>
```

## Components are functions

A component is a plain function: props in, JSX out. No classes, no hooks, no
lifecycle. Children arrive in the `children` prop.

```tsx
import { renderToString, type JSX } from "@vincle/core";

function Layout({ title, children }: { title: string; children: JSX.Element | JSX.Element[] }) {
  return (
    <html lang="en">
      <head>
        <title>{title}</title>
      </head>
      <body>{children}</body>
    </html>
  );
}

const html = await renderToString(
  <Layout title="My page">
    <h1>Welcome</h1>
    <p>This is a page rendered with Vincle.</p>
  </Layout>,
);
```

Same props, same output: components are easy to test and cache.

## Values are escaped

Every value is escaped for where it lands. `raw()` is the only way to emit
markup, for HTML you trust.

```tsx
import { renderToString, raw } from "@vincle/core";

// User input is HTML-escaped automatically
const userInput = '<script>alert("xss")</script>';
const html = await renderToString(<p>{userInput}</p>);

// Use raw() only for trusted HTML you generated yourself
const trustedHtml = "<em>rendered from your own markdown</em>";
const html2 = await renderToString(<article>{raw(trustedHtml)}</article>);
```

```html
<p>&lt;script&gt;alert("xss")&lt;/script&gt;</p>
<article><em>rendered from your own markdown</em></article>
```

See the [security model](/guide/security).

## Async works directly

Any component can be an `async` function. `renderToString` awaits the whole
tree, so the component that needs data is the component that fetches it.

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

declare const db: {
  users: {
    findById(id: string): Promise<{ name: string; email: string }>;
  };
};

async function UserCard({ id }: { id: string }) {
  const user = await db.users.findById(id);
  return (
    <div class="card">
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

const html = await renderToString(<UserCard id="42" />);
```

## Where to next

- [Elements & attributes](/guide/jsx/elements-attributes): `class`, `style`, booleans
- [Components](/guide/jsx/components): props, composition, async
- [Load data in your components](/guide/introduction#load-data-in-your-components): `await` inside render
