Components
A component is a plain function: props in, JSX out. No classes, no hooks, no lifecycle.
Basics#
import { renderToString } from "@vincle/core";
function Greeting({ name }: { name: string }) { return ( <p> Hello, <strong>{name}</strong>! </p> );}
const html = await renderToString(<Greeting name="Alice" />);<p>Hello, <strong>Alice</strong>!</p>Typed props and composition#
Props are typed with ordinary TypeScript. Children arrive in the children
prop, so one component's output becomes another's content:
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>,);Async components#
Any component can be async. renderToString awaits the whole tree:
async function Profile({ id }: { id: string }) { const user = await db.users.find(id); return ( <div class="profile"> <h1>{user.name}</h1> <p>{user.bio}</p> </div> );}Sibling async branches run in document order, one at a time.
What you don't need#
A render calls each component once and produces a string. Nothing re-renders,
so there is no state (it lives outside the render: database, request context),
no effects, and no React.memo. A Context.Provider supplies values during
the server render only.
Where to next#
- Fragments: grouping children without a DOM wrapper
- Elements & attributes: how JSX maps to HTML
- Context & Scope: share a value with a subtree (Provider) or a request (Scope)
- First render: your first component