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.
pnpm create uniweb my-site --template marketing
cd my-site
pnpm install
pnpm devOpen http://localhost:5173 to see your site. Edit files in site/pages/ and foundation/src/sections/ to see changes instantly.
Need pnpm? Run
npm install -g pnpmor see pnpm installation.
Run these from the project root (where pnpm-workspace.yaml is):
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.
The marketing template includes real components (Hero, Features, Pricing, Testimonials, FAQ, and more) with sample content—a working site you can explore and modify.
Other templates:
# Multilingual business site (English, Spanish, French)
pnpm create uniweb my-site --template international
# Academic site (researcher portfolios, lab pages)
pnpm create uniweb my-site --template academic
# Documentation site
pnpm create uniweb my-site --template docs
# Minimal starter (build from scratch)
pnpm create uniweb my-siteSee them live: View all template demos
Every project is a workspace with two packages:
site/— Content, pages, entry pointfoundation/— React components
Content authors work in markdown. Component authors work in React. Neither can break the other's work.
my-project/
├── site/ # Content + configuration
│ ├── pages/ # File-based routing
│ │ └── home/
│ │ ├── page.yml # Page metadata
│ │ └── hero.md # Section content
│ ├── locales/ # i18n (hash-based translations)
│ ├── main.js # Entry point (~6 lines)
│ ├── vite.config.js # 3-line config
│ └── public/ # Static assets
│
└── foundation/ # Your components
├── src/
│ ├── 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
├── 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.
---
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 code 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.
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
pnpm 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
foundation/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 Guide 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 foundation/src/sections/Hero.jsx. The component receives parsed content:
export default function Hero({ content }) {
const { title, paragraphs, links, imgs, 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 foundation/ folder ships with your project as a convenience, but a foundation is a self-contained artifact with no dependency on any specific site. Sites reference foundations by configuration, not by folder proximity.
Three ways to use a foundation:
| Mode | How it works | Best for |
|---|---|---|
| Local folder | Foundation lives in your workspace | Developing site and components together |
| npm package | pnpm add @acme/foundation |
Distributing via standard package tooling |
| Runtime link | Foundation loads from a URL | Independent release cycles, platform-managed sites |
You can delete the foundation/ 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. The multi template supports this workflow with multiple test sites exercising a shared foundation.
The structure you start with scales without rewrites:
-
Single project — One site, one foundation. Develop and deploy together. Most projects stay here.
-
Published foundation — Release your foundation as an npm package or to uniweb.app. Other sites can use it without copying code.
-
Multiple sites — Several sites share one foundation. Update components once, every site benefits.
-
Platform-managed sites — Sites built on uniweb.app with visual editing tools can use your foundation. You develop components locally; content teams work in the browser.
Start with local files deployed anywhere. The same foundation works across all these scenarios.
Developer Guides — Building foundations and components, converting designs, component patterns, theming contexts
Content Author Guides — Writing pages in markdown, writing dynamic content, site setup, theming, recipes
Reference Docs — Full reference for all configuration, commands, and framework
| Topic | Guide |
|---|---|
| Content Structure | How markdown becomes component props |
| Component Metadata | The meta.js schema |
| Site Configuration | site.yml reference |
| CLI Commands | create, build, docs, i18n |
| Templates | Built-in, official, and external templates |
| Deployment | Vercel, Netlify, Cloudflare, and more |
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
})Both templates use the same unified workspace configuration:
# pnpm-workspace.yaml
packages:
- 'site'
- 'foundation'
- 'sites/*'
- 'foundations/*'Also set in package.json for npm compatibility.
{
"workspaces": ["site", "foundation", "sites/*", "foundations/*"]
}This means no config changes when evolving from single to multi-site.
How 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. Content authors can't break components, and component updates don't require content changes.
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.
@uniweb/build— Foundation build tooling@uniweb/runtime— Foundation loader and orchestrator for sites@uniweb/templates— Official templates and template processing
Apache 2.0