A component content architecture for React. Build sites where content authors and component developers can't break each other's work—and scale from local files to visual editing without rewrites.
Create well-structured Vite + React projects with file-based routing, localization, and clean content/code separation out of the box.
npm create uniwebThe interactive prompt asks for a project name and template. Pick one, then:
cd my-project
pnpm install # or npm install
pnpm dev # or npm run devEdit files in site/pages/ and src/sections/ to see changes instantly.
pnpm ready —
pnpm create uniwebworks out of the box. Projects include bothpnpm-workspace.yamland npm workspaces.
| Template | Description |
|---|---|
| Starter | Foundation + site + sample content (default) |
| None | Foundation + site with no content |
| Marketing | Landing page, features, pricing, testimonials |
| Docs | Documentation with sidebar and search |
| Academic | Research site with publications and team |
| International | Multilingual site with i18n and blog |
| Dynamic | Live API data fetching with loading states |
| Store | E-commerce with product grid |
| Extensions | Multi-foundation with visual effects extension |
See them live: View all template demos
Use --blank for an empty workspace (no packages) — grow with uniweb add.
Two starting points. Either let the CLI create a new directory:
pnpm create uniweb my-site --template docs…or scaffold inside an existing directory (e.g., a freshly-cloned GitHub repo):
pnpm create uniweb . --template docsRun these from the project root:
pnpm dev # Start development server
pnpm build # Build foundation + site for production
pnpm preview # Preview the production buildThe build command outputs to site/dist/. With pre-rendering enabled (the default for official templates), you get static HTML files ready to deploy anywhere. For the actual deploy step (and the uniweb publish / uniweb deploy commands), see Deployment below.
Every project starts as a workspace with two packages:
site/— Content, pages, entry point (package name:site)src/— React components, the site's source code (package name:src)
A site is pure content. A foundation is the site's source code — that's why it lives in src/. Content authors work in markdown. Component authors work in React. Neither can break the other's work. As your project grows, use uniweb add to add more foundations, sites, and extensions.
my-project/
├── site/ # Content + configuration
│ ├── pages/ # File-based routing
│ │ └── home/
│ │ ├── page.yml # Page metadata
│ │ └── hero.md # Section content
│ ├── locales/ # i18n (hash-based translations)
│ ├── entry.js # Entry point (~6 lines)
│ ├── vite.config.js # 3-line config
│ └── public/ # Static assets
│
└── src/ # Foundation package (the site's source)
├── sections/ # Section types (addressable from markdown)
│ ├── Hero.jsx # Bare file → section type (no meta.js needed)
│ └── Features/
│ ├── meta.js # Content interface (params, presets)
│ └── Features.jsx
├── components/ # Regular React components
│ └── Button.jsx
├── main.js # Package exports (vars, defaultLayout, props)
├── styles.css # Tailwind v4 + CSS tokens
├── package.json # name: "src"
├── vite.config.js # 3-line config
└── dist/ # Built output
Pages are folders. Create pages/about/ with markdown files inside → visit /about. That's the whole routing model.
Batteries included: File-based routing, pre-rendering, localization, dynamic routes, media processing, search indexing, and more. See Building with Uniweb for the full list.
---
type: Hero
theme: dark
---
# Welcome
Build something great.
[Get Started](#)Frontmatter specifies the component type and configuration. The body contains the actual content—headings, paragraphs, links, images—which gets semantically parsed into structured data your component receives.
For content that doesn't fit markdown patterns—products, team members, events—use tagged data blocks:
```yaml:team-member
name: Sarah Chen
role: Lead Architect
```Access the parsed data via content.data:
function TeamCard({ content }) {
const member = content.data['team-member']
return (
<div>
{member.name} — {member.role}
</div>
)
}Natural content stays in markdown; structured data goes in tagged blocks (YAML or JSON).
export default function Hero({ content, params }) {
const { title, paragraphs, links } = content
return (
<section className="py-20 text-center">
<h1 className="text-4xl font-bold">{title}</h1>
<p className="text-xl text-gray-600">{paragraphs[0]}</p>
{links[0] && (
<a
href={links[0].href}
className="mt-8 px-6 py-3 bg-blue-600 text-white rounded inline-block"
>
{links[0].label}
</a>
)}
</section>
)
}Standard React. Standard Tailwind. The { content, params } interface is only for section types — components that content creators select in markdown frontmatter. Everything else uses regular React props.
Sections aren't limited to flat content. Content authors can embed interactive React components using markdown image syntax:
---
type: SplitContent
---
# See it in action
The architecture handles the hard parts so you can focus on what matters.
{period=30d}@PerformanceChart is a full React component — a ThreeJS animation, an interactive diagram, a live data visualization — placed by the content author, rendered in the component's visual slot via <Visual>. It looks like an image reference, but it can be anything.
Authors can also compose layouts from reusable section types using child sections:
pages/home/
├── highlights.md # type: Grid, columns: 3
├── @stats.md # type: StatCard
├── @testimonial.md # type: Testimonial
└── @demo.md # type: SplitContent (with an embedded @LiveDemo inset)
Three different section types, arranged in a grid, one with an interactive component inside it — all authored in markdown. The developer builds reusable pieces; the content author composes them. See the Component Patterns guide for the full composition model.
After creating your project:
-
Explore the structure — Browse
site/pages/to see how content is organized. Each page folder containspage.yml(metadata) and.mdfiles (sections). -
Generate component docs — Run
uniweb docsto createCOMPONENTS.mdwith all available components, their parameters, and presets. -
Learn the configuration — Run
uniweb docs siteoruniweb docs pagefor quick reference on configuration options. -
Create a section type — Add a file to
src/sections/(e.g.,Banner.jsx) and rebuild. Bare files at the root are discovered automatically — nometa.jsneeded. Addmeta.jswhen you want to declare params or presets. See the Component Metadata Reference for the full schema.
The meta.js file defines what content and parameters a component accepts. The runtime uses this metadata to apply defaults and guarantee content structure—no defensive null checks needed in your component code.
Open site/pages/home/hero.md and edit the headline:
---
type: Hero
---
# Your New Headline Here
Updated description text.
[Get Started](/about)Save and see the change instantly in your browser.
Open src/sections/Hero.jsx. The component receives parsed content:
export default function Hero({ content }) {
const { title, paragraphs, links, images, items } = content
// Edit the JSX below...
}The parser extracts semantic elements from markdown—title from the first heading, paragraphs from body text, links from [text](url), and so on. The items array contains child groups created when headings appear after content (useful for features, pricing tiers, team members, etc.).
The src/ folder (your project's foundation) ships with your project as a convenience, but a foundation is dynamically loaded — sites reference it by configuration, not by folder proximity.
Two ways to use a foundation:
| Mode | How it works | Best for |
|---|---|---|
| Local folder | Foundation lives in your workspace | Developing site and components together |
| Runtime link | Foundation loads from a URL | Independent release cycles, platform-managed sites |
You can delete the src/ folder entirely and point your site at a published foundation. Or develop a foundation locally, then publish it for other sites to consume. The site doesn't care where its components come from.
This enables two development patterns:
Site-first — You're building a website. The foundation is your component library, co-developed with the site. This is the common case.
Foundation-first — You're building a component system. The site is a test harness with sample content. The real sites live elsewhere—other repositories, other teams, or managed on uniweb.app. Use uniweb add site to add multiple test sites exercising a shared foundation.
Install the CLI globally with
npm i -g uniwebfor the best experience. You can also usenpx uniweborpnpm uniwebwithout a global install.
Start simple. Add what you need, when you need it:
# Create a blank workspace
uniweb create my-workspace --blank
cd my-workspace
# Add a co-located foundation + site pair
uniweb add project blog
# Add a second foundation at root
uniweb add foundation DocKit
# Add a site wired to a specific foundation
uniweb add site docs --foundation DocKit
# Add an extension (auto-wired to the only site)
uniweb add extension effects
# Scaffold + apply content from an official template
uniweb add project marketing --from marketing
# Install dependencies and run in dev mode
pnpm install # or npm install
pnpm dev # or npm run devThe workspace grows organically. add handles placement, wires dependencies, updates workspace globs, and generates root scripts. The name you provide becomes both the directory name and the package name. Use --path to override default placement, or --project for explicit co-located layouts.
A Uniweb project produces two artifacts — a site (content) and a foundation (code). Who manages the content decides what you ship:
- If you (or your dev team) manage the content, you ship them together — the site and its foundation, bundled or linked.
- If someone else manages the content, you ship only the foundation. Content authors compose sites in the Uniweb apps; there's no site to deploy from your repo.
You (or your dev team) write the markdown. Deploy site + foundation together.
The shortest path to a live site is free, on GitHub Pages, with a custom domain:
uniweb create . # from within a freshly-cloned GitHub repo
uniweb add ci --host=github-pages
# Commit, push to GitHub, enable Pages in repo settings → live siteuniweb add ci scaffolds a GitHub Actions workflow that runs uniweb build on each push. Pre-rendering is on by default — static HTML, fast first paint, SEO out of the box. Use --domain=<domain> for a custom domain.
Other free static hosts. Cloudflare Pages, Netlify, and Vercel auto-build your site when you connect the repo through their dashboard — no scaffolded workflow needed.
When to choose Uniweb hosting instead (paid): when you need dynamic-page prerender at the edge (for collections fetched at runtime, not just at build time), foundation/runtime version propagation without redeploying every site, or edge SSR. If your site's content lives in markdown and updates ship via git, free CI is the right call.
You're building a foundation for clients, content authors, or any team that won't write markdown. The foundation is your product; the repo's site/ is a test harness for the code (run pnpm dev to preview your components against sample content). You don't deploy a site — you publish a foundation:
cd src
uniweb publish @your-org/foundation-nameReal sites built on your foundation are managed in the Uniweb apps (web + desktop) — visual editors designed for non-technical authors. They never see git, markdown, yaml, or React. They see your components, with live previews and visual controls for the params you defined. The foundation becomes the editor's native vocabulary for that site: you keep creative control of the design system, they get an editor that feels custom-built for them.
This is the best path when site content has a life independent of the foundation's release cycle — agencies, design studios, multi-client teams, or any project where content authors aren't the same people as the developers.
A future version will let markdown in a git repo and content in the Uniweb apps stay in two-way sync. Authors edit visually, devs edit in their IDE, both surfaces work on the same content. On the roadmap; not available today.
| Command | What it does |
|---|---|
uniweb add ci --host=<adapter> |
Scaffold a CI workflow in your repo (today: github-pages). The host runs uniweb build on each push. |
uniweb deploy |
Deploy to Uniweb hosting (default). With --host=<adapter>, push directly to a static host — builds, uploads, invalidates in one step. |
uniweb export |
Produce a self-contained dist/ for any static host. You upload it yourself. --host=<adapter> adds host-specific helper files. |
uniweb publish @org/name |
Publish a foundation to the registry (path 2). |
uniweb build |
Inspect a build locally. For shipping, use deploy or export. |
uniweb update |
Reconcile workspace state with the CLI: self-update the global install, align @uniweb/* deps in every package.json to the CLI's matrix, refresh AGENTS.md. Refuses to outpace declared deps with a stale doc. |
--host=<adapter> is the same option across deploy, export, and add ci. Built-in adapters: cloudflare-pages, netlify, github-pages, vercel, s3-cloudfront, generic-static. Each adapter implements only the operations it supports — add ci is currently github-pages-only because it's the only one that needs a workflow file in the repo.
Both paths use the same framework. The difference is who edits content, where it lives, and what gets deployed. For the deeper menu — site-bound vs cataloged foundations, S3+CloudFront, manual exports, per-host recipes, foundation propagation — see → Deploying.
Full documentation is available at github.com/uniweb/docs:
| Section | Topics |
|---|---|
| Getting Started | Introduction, quickstart, templates |
| Authoring | Writing content, site setup, theming, collections, translations |
| Development | Building foundations, component patterns, data fetching, layouts |
| Reference | Configuration files, kit API, CLI commands, deployment |
| Topic | Guide |
|---|---|
| Content Structure | How markdown becomes component props |
| Component Metadata | The meta.js schema |
| Site Configuration | site.yml reference |
| CLI Commands | All CLI commands and flags |
| Templates | Built-in, official, and external templates |
| Deployment | Two artifacts, two verbs — bundled, linked, and per-host recipes |
The defineSiteConfig() function handles all Vite configuration for sites:
import { defineSiteConfig } from '@uniweb/build/site'
export default defineSiteConfig({
// All options are optional
tailwind: true, // Enable Tailwind CSS v4 (default: true)
plugins: [], // Additional Vite plugins
// ...any other Vite config options
})The defineFoundationConfig() function handles all Vite configuration for foundations:
import { defineFoundationConfig } from '@uniweb/build'
export default defineFoundationConfig({
// All options are optional - entry is auto-generated
fileName: 'foundation', // Output file name
externals: [], // Additional packages to externalize
includeDefaultExternals: true, // Include react, @uniweb/core, etc.
tailwind: true, // Enable Tailwind CSS v4 Vite plugin
sourcemap: true, // Generate sourcemaps
plugins: [], // Additional Vite plugins
build: {}, // Additional Vite build options
// ...any other Vite config options
})For Tailwind CSS v3 projects, set tailwind: false and use PostCSS:
export default defineFoundationConfig({
tailwind: false, // Uses PostCSS instead of Vite plugin
})A default project has two packages, listed in both pnpm-workspace.yaml and package.json:
# pnpm-workspace.yaml
packages:
- src
- siteIn package.json (for npm compatibility):
{
"workspaces": ["src", "site"]
}When you add more packages, the CLI adds the appropriate workspaces automatically:
# After: uniweb add site marketing/blog
# After: uniweb add foundation marketing/blogger
packages:
- src
- site
- marketing/blog
- marketing/bloggerHow is this different from MDX?
MDX blends markdown and JSX—content authors write code. Uniweb keeps them separate: content stays in markdown, components stay in React. Authors can still embed interactive components (via  syntax), but they never see JSX. Component updates don't require content changes, and content changes can't break components.
How is this different from Astro?
Astro is a static site generator. Uniweb is a component content architecture that works with any deployment (static, SSR, or platform-managed). The foundation model means components are portable across sites and ready for integration with visual editors.
Do I need uniweb.app?
No. Local markdown files work great for developer-managed sites. The platform adds dynamic content, visual editing, and team collaboration when you need it.
Can I use an existing component library?
Yes. Foundations are standard React. Import any library into your foundation components. The { content, params } interface only applies to section types (components with meta.js) — everything else uses regular React props.
Is this SEO-friendly?
Yes. Content is pre-embedded in the initial HTML—no fetch waterfalls, no layout shifts. Meta tags are generated per page. SSG is supported by default.
What about dynamic routes?
Pages can define data sources that auto-generate subroutes. A /blog page can have an index and a [slug] template that renders each post.
Is Uniweb good for documentation sites?
Yes — documentation is a natural fit. Content stays in markdown (easy to version, review, and contribute to), while the foundation handles navigation, search, and rendering. Uniweb's own docs use this pattern: pure markdown in a public repo, rendered by a separate foundation.
@uniweb/build— Foundation build tooling@uniweb/runtime— Foundation loader and orchestrator for sites@uniweb/templates— Official project templates (content only, CLI provides structure)
Apache 2.0