Docs
Quickstart
Create a Proa site, run it, and edit your first route in five minutes.
This quickstart gets you from an empty directory to a hello world Proa. It should take about five minutes once Rust and the Proa CLI are ready.
Building with an AI agent? The complete documentation is served at /llms-full.txt for agent consumption. LLM docs lists the other machine-readable interfaces.
1. Install Proa
Install the latest stable CLI from crates.io using a current Rust toolchain:
cargo install proa-cli --locked
Run the same command to update an existing Cargo installation.
Proa is public; no GitHub credential or Cargo publishing token is needed.
Troubleshooting covers dependency resolution
failures. The Basic app also requires the Tailwind CSS CLI on your PATH;
see Tailwind prerequisite before
continuing.
2. Create A Site
A minimal Axum-backed application with the core boundaries in place:
proa new site hello-proa --yes
Cargo.tomlproa.config.jsonproa.lock.jsonbuild.rssrc/components/endpoints/layouts/pages/app.rsmain.rsroutes.rsmigrations/public/favicon.svgstyles.cssstyles/input.csstests/Checkpoint: Cargo.toml, src/main.rs, src/pages/home.rs, and
src/layouts/root.rs should exist.
The kitchen-sink starter, and the fastest way to see a production Proa app fit together:
proa new site origin-atlas --template origin --yes
This scaffolds ORIGIN, a live routing atlas that exercises the whole
framework: a custom RIPEstat DataLoader with request-scoped deduplication
and a TTL cache, streamed Suspense sections, proa-ui components that each
carry a Markdown representation (the same page serves /site/<host>.md for
agents), rsjs islands for the interactive surfaces, Tailwind styling with a
themed token set, self-hosted fonts, and a golden test that pins the
server-rendered document byte for byte. The generated README.md doubles as
an editing guide.
Checkpoint: src/lib.rs, src/loader.rs, and src/components/ should
exist, and proa dev --open shows the atlas.
You do not have to build the common UI yourself: proa-ui is a shadcn-style catalog of ready-made Proa components, and proa ui add button vendors the source into src/components/ where you own and edit it like any other file. Worth knowing before you write your first card, dialog, or form by hand.
The remaining steps use the Basic app named hello-proa. If you chose
ORIGIN, follow its generated README.md and run commands inside origin-atlas.
If styles/starter.css already exists and styles/input.css imports it,
continue to the next step.
My starter has no styles/starter.css yet
Inside hello-proa, preserve the starter design before Tailwind rebuilds
public/styles.css:
- Move
public/styles.csstostyles/starter.css. Ifstyles/starter.cssalready exists, keep that file. - Replace the contents of
styles/input.csswith:
@layer theme, base, proa-compat, components, utilities;
@import "tailwindcss";
@import "./starter.css" layer(components);
@source "../src/**/*.rs";
Tailwind will regenerate public/styles.css with the starter design included.
3. Run The Server
cd hello-proa && proa dev --open
proa dev builds configured assets, runs the linked RSJS pipeline, starts the
Axum server on http://localhost:3000, and restarts
when Rust, style, or config files change.
The first build downloads dependencies and compiles the Rust crates, which can
take several minutes. The terminal reports each build stage; --open waits
until the server accepts connections before opening your browser.
Open tabs reload themselves once the rebuilt server is accepting connections
again, so you do not have to refresh by hand. Stylesheet edits are applied
without navigating, which keeps scroll position and form state. Turn it off or
retune it with --no-reload, --no-css-hot-swap, and --debounce-ms; see
Live reload.
4. Edit The First Page
Set up your editor for Rust navigation and completion, including expressions inside raw Markdown templates.
Open src/pages/home.rs. Inside impl<L: DataLoader> WebRender<L> for HomePage, find the <section class="hero"> in the existing html! macro.
Replace only its <h1> and <p class="lede"> with these two elements:
<h1>"Fast SSR, typed in Rust"</h1>
<p class="lede">"This page renders from a compiled template macro."</p>
Keep the surrounding template, including DatabaseBadge and DatabaseCard.
Those children load the database status, so this page uses async WebRender
and html!. A static component can use WebRenderSync and html_sync!, as
shown by the About page below.
In the same file, find impl<L: DataLoader> MdRender<L> for HomePage.
Inside render_md's existing md! { r###"…"### } template, replace the old
heading and the paragraph immediately after it with these Markdown lines:
Fast SSR, typed in Rust
This page renders from a compiled template macro.
Keep the other Markdown content and child expressions. Proa's content-coverage lint compares rendered content across the two implementations; changing both keeps browsers and agents informed of the same page edit.
Refresh the browser and open /index.md to see
both representations. Edit styles/starter.css to change the starter's design;
public/styles.css is the generated output and is rebuilt by Tailwind.
5. Add A Route
Create src/pages/about.rs:
use axum::response::IntoResponse;
use proa_core::{to_async, DataLoader, WebContext, WebRenderSync, WriteError};
use proa_framework_axum::RouteResponse;
use proa_macros::html_sync;
use crate::layouts::RootDocument;
pub async fn handler() -> impl IntoResponse {
RouteResponse::ssr_async(RootDocument {
title: "About",
content: to_async(AboutPage {
heading: "About",
body: "This route renders from src/pages/about.rs.",
}),
})
}
struct AboutPage {
heading: &'static str,
body: &'static str,
}
impl<L: DataLoader> WebRenderSync<L> for AboutPage {
fn render(self, cx: &mut WebContext<L>) -> Result<(), WriteError> {
html_sync! {
<main data-component="about-page" class="mx-auto max-w-3xl px-6 py-16">
<h1 class="text-4xl font-semibold">{self.heading}</h1>
<p class="mt-4 text-zinc-600">{self.body}</p>
</main>
}
.render(cx)
}
}
Export it from src/pages/mod.rs, keeping the generated home module:
pub mod about;
pub mod home;
Register it in src/routes.rs, between the markers the generator maintains:
let (router, metadata) = router_from_spec(vec![
// proa:route-specs:start
Route::endpoint("/", get(pages::home::handler)),
Route::endpoint("/about", get(pages::about::handler)),
// proa:route-specs:end
]);
That is the whole route flow. RouteResponse renders the page and finalizes
the HTTP response, RootDocument supplies the shared page shell (it is
async-capable, so a synchronous page goes in through to_async), and the
fields passed to AboutPage are compile-time-checked props. Add
page caching to the route spec when the
response should carry CDN cache headers. Add Axum extractors such as Path,
Query, or State to handler when the route needs request data.
Open http://localhost:3000/about. Check the route table:
proa site routes
Checkpoint: /about appears in the route report and renders in the browser.
6. Validate Before Moving On
Run Proa's source-aware checks before the full build:
proa fmt --check --verify src
proa lint src --deny warnings
proa test
proa build
proa fmt --check --verify checks the canonical template layout and verifies
that formatting would preserve the rendered output. proa lint understands
Proa render paths, so --deny warnings catches performance problems such as
intermediate rendered strings, avoidable allocations, and async renderers that
never await. Once those focused checks are clean, proa build validates the
complete linked application. proa test also checks both page representations
and that the two database components share one origin query.
Next steps
- Tutorial
- Add typed data, components, routes, and a form action.
- proa new site
- Scaffold a project and see where pages, layouts, components, and assets live.
- HTML rendering
- Render typed Rust values to HTML with render traits and the html! macros.
- Troubleshooting
- Fix the most common Proa setup, registry, template, component, island, routing, and CI failures.
- Deploy
- Pick a deployment target, then follow the guide for it.