SSR-first JSX for Node.js

File-based routing. Async server components. Zero client JavaScript by default. Opt in to fine-grained reactivity one island at a time.

npx davaux create my-app
cd my-app && npm install
npm run dev

File-based routing

The filename encodes the URL and HTTP method. blog/[slug].page.tsx becomes GET /blog/:slug. No config, no boilerplate.

Async JSX everywhere

Any component can await data — no getServerSideProps, no loader functions. The server renders the full tree before sending a byte.

Islands + signals

Write a component once. The framework compiles it for both server and client automatically. Fine-grained signals update only the exact DOM nodes that changed — no VDOM, no re-renders.

Layouts that nest

Drop _layout.tsx in any directory. Layouts compose from outermost to innermost automatically. Each layout gets ctx.head after the page has run, so titles and meta tags are always accurate.

Middleware that scopes

_middleware.ts applies to routes in its directory. src/middleware.ts runs on every request, including 404s. Auth, logging, rate limiting — each wired once and applied exactly where you want it.

Co-located form actions

Export an action from any page file to handle its form submissions. No separate API route needed — validation, mutation, and re-render all live in one place.

Head management

Set ctx.head.title, ctx.head.description, and arbitrary meta tags from any page or layout. Values are always resolved by the time the root layout renders.

Partial updates

Stream slow content into an already-sent HTML shell with ctx.defer(). Built on the browser's native Declarative Partial Updates API — no client JavaScript required.

Custom error pages

Drop _error.tsx in your routes directory to handle any 404 or 500 with your own design. Error pages inherit layouts and have full access to ctx.

Static generation built in

davaux static pre-renders every page to plain HTML. Dynamic routes declare their paths with getStaticPaths. Deploy to any CDN — no Node.js required at runtime.

A page in 10 lines

// src/routes/blog/[slug].page.tsx
import { definePage, type ExtractParams } from 'davaux'
import { getPost } from '~/data/posts'

export default definePage<ExtractParams<'blog/[slug]'>>(async (ctx) => {
  const post = await getPost(ctx.params.slug)
  ctx.head.title = post.title

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  )
})
Read the docs →