# Building with Uniweb

You know how to build a React app. You pick a bundler (Vite), write components, set up routes, and wire everything together. A typical single-page application (SPA) needs a router config, lazy imports, maybe a layout system. In Uniweb, the wiring is the file structure itself — markdown files name the components they want, folders define routes, and the build connects them.

This guide shows what a Uniweb project looks like and why almost everything in it is standard React and Vite. The unfamiliar part is small and mechanical.

---

## The Two Packages

A Uniweb project is a workspace with two packages (or more, as it grows):

```
my-project/
├── src/                     # React components (Vite library build) — the foundation
│   └── sections/
│       └── Hero.jsx
├── site/                    # Content + routing (Vite app)
│   └── pages/
│       └── home/
│           └── hero.md
└── pnpm-workspace.yaml
```

**Foundation** is a standard Vite library project, living in `src/` (the *site's source code* — that's the framing). It builds to a single `entry.js` bundle plus a `meta/schema.json` that describes what's inside. Your React code lives here — components, hooks, styles, whatever you need.

**Site** is a standard Vite app with file-based routing. Each folder in `pages/` is a route. Markdown files inside a folder define the sections on that page. The runtime loads the foundation and renders the right component for each section.

Both packages are independent. A foundation can serve multiple sites. A site can swap foundations. You can have several of each in one workspace — or just one of either, depending on what you're building.

---

## The Connection

Here's the complete round trip — a component and the markdown that addresses it.

**The component** — a bare `.jsx` file in `sections/`:

```jsx
export default function Hero({ content }) {
  return (
    <div className="py-20 text-center max-w-4xl mx-auto px-6">
      <h1 className="text-4xl font-bold">{content.title}</h1>
      <p className="text-xl mt-4">{content.paragraphs[0]}</p>
      {content.links[0] && (
        <a
          href={content.links[0].href}
          className="inline-block mt-8 px-6 py-3 bg-blue-600 text-white rounded-lg"
        >
          {content.links[0].label}
        </a>
      )}
    </div>
  )
}
```

**The markdown** that uses it:

```markdown
---
type: Hero
---

# Welcome to Our Product

Create something great with less effort.

[Get Started](/about)
```

**What arrives in `content`:**

| Markdown element | Content field | Value |
| --- | --- | --- |
| `# Welcome to...` | `content.title` | `"Welcome to Our Product"` |
| Paragraph text | `content.paragraphs[0]` | `"Create something great..."` |
| `[Get Started](/about)` | `content.links[0]` | `{ href: "/about", label: "Get Started" }` |

The parser extracts semantic elements from markdown — headings become titles, paragraph text becomes `paragraphs`, links become `links`, images become `images`. Your component reads the structure it needs and ignores the rest. See [Content Structure](../reference/content-structure.md) for the full mapping.

The component is a regular React function. It receives `content` (parsed markdown), `params` (configuration from `meta.js`, if you add one), and `block` (the runtime object for this section). For a first component, `content` is all you need.

---

## That's Component Content Architecture

Uniweb calls this pattern **Component Content Architecture** (CCA) — components and content are separate artifacts connected by convention, not code. The architecture is the file structure. There's no component registry to maintain. No router config to update. No mapping between URLs and components.

- **`sections/`** is the component catalog. The build discovers every `.jsx` file and folder at the root automatically. A file named `Hero.jsx` becomes a section type called "Hero" — content authors reference it with `type: Hero` in frontmatter.

- **`pages/`** is the routing table. Each folder is a route. `pages/home/` → `/`, `pages/about/` → `/about`, `pages/blog/` → `/blog`.

- **Frontmatter** is the connection. `type: Hero` in a markdown file tells the runtime which component to render. That's the entire binding mechanism.

Adding a component means dropping a file in `sections/`. Adding a page means creating a folder in `pages/`. No configuration files to update, no imports to add.

The build scans `src/sections/` for section types. If you need additional search paths, configure them via the `sections` option in `defineFoundationConfig()` — those extra paths require `meta.js` for addressability.

---

## What You Get

Without extra configuration, a Uniweb project gives you:

- **File-based routing** — folder = route, no `react-router` config needed
- **Hot reload** on both content and component changes
- **Pre-rendered static HTML** for every page, with client-side hydration
- **Localization** — locale detection, locale-aware URLs, translation extraction and matching
- **Parametric pages** — a `[slug]` folder becomes one page per record of a query
- **Records** — markdown, YAML or JSON files in `records/` become structured data, with automatic i18n support
- **Media processing** — image optimization, video poster generation, PDF preview thumbnails
- **Search indexing** — content is indexed at build time for client-side search
- **Section backgrounds** — image, video, gradient, and color backgrounds rendered by the engine (components don't repeat this logic)
- **Semantic theming** — CSS tokens that resolve per-section based on context, controlled by the site

In a plain Vite + React app, you'd wire each of these yourself: install a router, write route definitions, set up lazy imports, build an SSG pipeline, add an i18n library, write slug-based route expansion, set up image processing, integrate a search engine. Uniweb handles all of this through project structure and build conventions — you opt into features by using them, not by configuring them.

You still write React. You still use Tailwind (or CSS, or whatever you prefer). You still structure your components however makes sense. The framework handles the infrastructure. Your job is the components.

---

## Growing From Here

Everything below is opt-in. A foundation that's just bare files in `sections/` with no `meta.js`, no params, and no theming is a valid foundation. Each addition solves a specific problem when you're ready for it.

### More section types

Add more files to `sections/`. Each `.jsx` file or folder at the root becomes a section type:

```
src/sections/
├── Hero.jsx
├── Features.jsx
├── Testimonial.jsx
├── CallToAction.jsx
├── Header.jsx
└── Footer.jsx
```

Content authors can now use `type: Hero`, `type: Features`, `type: Testimonial`, and so on. Header and Footer go in the site's `layout/header.md` and `layout/footer.md` to render on every page.

### Configuration with params

When a component needs options — layout style, column count, whether to show an image — promote the bare file to a folder and add `meta.js`:

```
src/sections/
└── Hero/
    ├── meta.js
    └── Hero.jsx
```

```js
// meta.js
export default {
  params: {
    layout: {
      type: 'select',
      label: 'Layout',
      options: ['centered', 'split'],
      default: 'centered',
    },
  },
}
```

```jsx
// Hero.jsx
export default function Hero({ content, params }) {
  if (params.layout === 'split') {
    return (
      <div className="grid md:grid-cols-2 gap-12 items-center py-20 px-6">
        <div>
          <h1 className="text-4xl font-bold">{content.title}</h1>
          <p className="text-xl mt-4">{content.paragraphs[0]}</p>
        </div>
        {content.images[0] && (
          <img
            src={content.images[0].src}
            alt={content.images[0].alt}
            className="rounded-lg"
          />
        )}
      </div>
    )
  }

  return (
    <div className="py-20 text-center max-w-4xl mx-auto px-6">
      <h1 className="text-4xl font-bold">{content.title}</h1>
      <p className="text-xl mt-4">{content.paragraphs[0]}</p>
    </div>
  )
}
```

Content authors set it in frontmatter:

```markdown
---
type: Hero
layout: split
---
```

Params force you to ask "what actually varies in this component?" — and that question produces a tighter interface. It's the same discipline as writing testable code: the constraint improves the design.

### Content-driven components

The progression from hardcoded to content-driven is natural. You start with a component that has its text in JSX. When that text needs to change without a code deploy, you move it to markdown and read from `content`. The [Converting Existing Designs](./converting-existing.md) guide walks through this step by step.

### Theme

A foundation declares what's customizable. A site sets the values. CSS variables bridge them:

```js
// src/main.js
export default {
  vars: {
    'header-height': { default: '4rem' },
    'max-width': { default: '80rem' },
  },
}
```

```yaml
# site/theme.yml
vars:
  header-height: 5rem
```

Components use `var(--header-height)` and adapt automatically. The [Thinking in Contexts](./thinking-in-contexts.md) guide covers the full theming system — semantic tokens, context classes, and how components adapt to light/dark/medium contexts without hardcoding colors.

### Site-aware components with kit

When a component needs site context — navigation, locale, routing — you import hooks from `@uniweb/kit`:

```jsx
import { useWebsite, useActiveRoute } from '@uniweb/kit'

function Header({ content }) {
  const { website } = useWebsite()
  const { isActiveOrAncestor } = useActiveRoute()

  const pages = website.getPageHierarchy({ for: 'header' })

  return (
    <header>
      <span>{website.name}</span>
      <nav>
        {pages.map(page => (
          <a
            key={page.id}
            href={page.route}
            className={isActiveOrAncestor(page) ? 'active' : ''}
          >
            {page.label}
          </a>
        ))}
      </nav>
    </header>
  )
}
```

Kit provides hooks (`useWebsite`, `useActiveRoute`, `useVersion`, `useAppearance`), primitive components (`Link`, `Image`, `Icon`), and utilities (`getLocaleLabel`). The data classes underneath — Website, Page, Block from `@uniweb/core` — are what hooks return. You access them through kit, not by importing core directly. See [Kit Reference](../reference/kit-reference.md) for the full API.

### Variants and the Front Desk

When one section type needs multiple layouts (a homepage hero vs. a pricing hero), the Front Desk pattern keeps a single section type with a `variant` param that delegates to different renderers. One component in the author's palette, multiple implementations underneath. See [Component Patterns](./component-patterns.md) for the full pattern.

---

## See Also

- **[Quickstart](../getting-started/quickstart.md)** — Step-by-step project creation (`uniweb create`)
- **[Content Structure](../reference/content-structure.md)** — How markdown becomes `content.title`, `content.items`, etc.
- **[Component Metadata](../reference/component-metadata.md)** — Full `meta.js` reference (params, content expectations, presets)
- **[Converting Existing Designs](./converting-existing.md)** — Bringing existing React code into a foundation
- **[Component Patterns](./component-patterns.md)** — Front Desk, Building Blocks, and other design patterns
- **[Thinking in Contexts](./thinking-in-contexts.md)** — Semantic theming and how components adapt to any brand
- **[Project Structures](./project-structures.md)** — Workspace layouts for multi-site, extension, and co-located projects