Docs menu

React, Vue and MDX
JavaScript only where you ask for it.

Since 0.10.0, you can write layouts as React or Vue components and pages as MDX. vagejs turns them into plain HTML when it builds the site, so pages send no JavaScript. Since 0.11.0, a React component can also run in the browser when you mark it interactive.

You install only what you use. Sites that don't use components stay exactly as they are. For a full worked example, see how to add an interactive React component to a static site, and for an overview, React, Vue and MDX in vagejs.

React layouts

Install React:

terminal
npm install react react-dom

Then write a layout as src/layouts/default.tsx. It gets the same values as any layout (content, page, site, pages, menus):

import type { LayoutProps } from "vagejs";

export default function Layout({ content, page, site }: LayoutProps) {
  return (
    <html lang={page.lang}>
      <head>
        <meta charSet="utf-8" />
        <title>{`${page.title} | ${site.title}`}</title>
      </head>
      <body>
        <main dangerouslySetInnerHTML={{ __html: content }} />
      </body>
    </html>
  );
}

content is your page, already turned into safe HTML, so insert it with dangerouslySetInnerHTML. React escapes everything else. vagejs still adds the description, canonical link, Open Graph tags and structured data to the <head>.

Vue layouts

Install Vue and its Vite plugin:

terminal
npm install vue
npm install --save-dev @vitejs/plugin-vue

Then write src/layouts/default.vue. Declare the values you use with defineProps, and insert the page with v-html:

<script setup lang="ts">
defineProps<{ content: string; page: { title: string; lang: string } }>();
</script>

<template>
  <html :lang="page.lang">
    <head><title>{{ page.title }}</title></head>
    <body><main v-html="content"></main></body>
  </html>
</template>

MDX pages

MDX is Markdown that can use components. Install it with React:

terminal
npm install react react-dom
npm install --save-dev @mdx-js/mdx

Then write a page ending in .mdx, such as src/pages/pricing.mdx. It has the same frontmatter as any page, and can import React components from src/components/:

---
title: Pricing
description: Plans and prices.
---
import PlanCard from "../components/PlanCard.tsx";

# Pricing

<PlanCard name="Team" price="$20 a month" />

MDX pages get the same addresses, head tags, sitemap entries and checks as Markdown pages. Components inside MDX must be React components. Vue components work in layouts only.

Interactive components

Most components only need to become HTML. When one needs to respond to visitors, such as a counter, a filter or a calculator, mark it with a client: directive where you use it:

import Calculator from "../components/Calculator.tsx";

<Calculator client:load rate={4.5} />
Directive Starts
client:load as soon as the page loads
client:visible when it scrolls into view
client:idle when the browser has nothing else to do

The component is still turned into HTML first, so visitors see it straight away and the page works before the JavaScript arrives. vagejs then sends the component's code and React to pages that use it, and adds one small loader script. Pages without interactive components still send none.

Rules:

  • The component must be a React function component exported from a file in src/components/.
  • Its props must be plain data: text, numbers, true or false, lists and objects. Functions can't be sent to the browser.
  • It can't have content between its tags yet. Pass text as a prop instead.
  • It can't contain another interactive component. Mark only the outermost one.
  • Files it imports other than JavaScript, such as CSS or images, aren't published. Use paths to files in public/ instead.
  • It works in MDX pages and React layouts. Vue components can't be interactive yet.

React itself is about 215 KB of JavaScript. That's more than the default 100 KB budget in vage audit, which counts it, so raise performance.budgets.javascriptKb if you accept the cost. When plain HTML can do the job (a <details> element, a link, a form), it's lighter and works everywhere.

Things to know

  • Only marked components run in the browser. Everything else is turned into HTML once, when the site is built. In those, useState, effects and onClick do nothing.
  • MDX pages are code. An .mdx page can import and run anything when the site is built, just like a layout. vagejs doesn't clean it up the way it cleans HTML in .md pages. Use MDX only for content you'd trust in a layout, and keep other writers on .md. vage check still checks what MDX pages produce: links, the link lock, hidden links and SEO.
  • Styles go in public/. CSS inside components (a Vue <style> block, or import "./card.css") isn't added to the site. Put your CSS in public/ and link it from the layout.
  • Your own structured data. vagejs adds JSON-LD for you. If a component layout writes its own, insert it with dangerouslySetInnerHTML (React) or v-html (Vue): Vue escapes the quotes in a plain <script> in a template, which breaks the JSON.
  • 404.mdx works as the page shown for addresses that don't exist, like 404.md.
  • The editor lists .md pages only. Edit .mdx pages in your code editor.
  • Missing packages are named. If a site uses a .vue layout or an .mdx page without the package installed, the build stops and prints the install command.

Start with one command.
You'll have a site running in a minute.

npm create vagejs@latest my-site

Read the docsUse it with your AI assistant