# React with Vite

> How midcode starts a Vite project with your own config plus one plugin, what it marks in your JSX and what you can edit, with Preact, Solid and Qwik.

- Page: https://midcode.app/docs/frameworks/react-vite
- From the midcode docs. Every page as Markdown: https://midcode.app/llms.txt

midcode edits a React app built with Vite in full: text, styles, images, structure, components with variants and states, and variables, all written into your `.tsx` and `.jsx` files. It starts Vite with your own config and one extra plugin, so the canvas shows your real app.

Compared with [Next.js](https://midcode.app/docs/frameworks/nextjs.md), three things are missing in the released version: making pages, Site settings and the free canvas. The next release brings Site settings, written into your `index.html`, new pages where the routes are files, and [the free canvas](https://midcode.app/docs/editor/free-canvas.md), kept in `src/midcode-scratch.tsx`. A Vite app whose router is written in code has no list of pages to read.

## At a glance

| | React with Vite |
| --- | --- |
| Detected by | `vite` in `package.json`, together with `react` (or `preact`, `solid-js`) |
| Runs with | `vite dev`, with a config of midcode's that loads yours and adds one plugin |
| Elements are marked by | That plugin, as Vite transforms each `.tsx` and `.jsx` file |
| Editing | Full, in `.tsx` and `.jsx` files |
| Components | Yes: instances, props, variants and states, variables |
| Pages | None listed, unless the routes are files (TanStack Router, Qwik City: next release). New pages only there |
| Styles | Tailwind 4 classes, or `mid:` classes with `midcode.css` imported in the module `index.html` loads |
| Tried with | Vite + React with Tailwind 4, without Tailwind and with Tailwind 3 |

## How midcode runs it

midcode reads `package.json`. `next`, `@react-router/dev`, `@remix-run/dev`, `astro`, `@sveltejs/kit` and `nuxt` are checked first, each with its own page in these docs. If none is there and `vite` is, the project is a Vite project. It's editable when `react`, `preact` or `solid-js` is a dependency too. (With `svelte` or `vue` instead, see [Svelte and SvelteKit](https://midcode.app/docs/frameworks/svelte.md) and [Vue](https://midcode.app/docs/frameworks/vue.md).)

midcode then starts Vite itself, with your project's own `vite` and the `node` of your login shell (midcode's own Node if the Mac has none):

```bash
vite dev /Users/you/sites/dashboard \
  --config "/Users/you/Library/Application Support/midcode/injected/vite.config.mjs" \
  --port 4310 --strictPort --host localhost
```

The port is the first free one from 4310 up. The dev server log (the button with the status dot in the top bar) shows the command and the Node that runs it.

The `--config` is what makes the canvas editable. It points at a file in midcode's own data folder, outside your project. This is the part of it that matters (`root` is your project's folder, `entry('vite')` finds the Vite in your `node_modules`, and the two variables hold the path of your config and of midcode's plugin):

```js title="~/Library/Application Support/midcode/injected/vite.config.mjs"
// Written by midcode: the project's Vite config, plus the midcode plugin.
export default async (env) => {
  const vite = await import(entry('vite'))
  const user = await vite.loadConfigFromFile(env, process.env.MIDCODE_USER_CONFIG || undefined, root)
  const { midcode } = await import(pathToFileURL(process.env.MIDCODE_VITE_PLUGIN).href)
  const config = user?.config ?? {}
  return { ...config, root: config.root ?? root, plugins: [midcode(root), ...(config.plugins ?? [])] }
}
```

Your `vite.config` (`.ts`, `.mts`, `.js`, `.mjs`, `.cjs` or `.cts`, at the project root) is loaded by your own Vite and returned as it is, with one more plugin at the front. Your file is never modified and nothing is added to `package.json`. The plugin only applies while serving, so it can't take part in a build, and it only exists in the dev server midcode started.

If your config serves over HTTPS (a certificate plugin), midcode reads the address from Vite's `Local:` line and accepts a self-signed certificate for localhost.

### Options from your dev script (next release)

What your `dev` script asks of Vite is kept. With `"dev": "vite --mode staging --base /app/"`, midcode starts `vite dev … --mode staging --base /app/`. The port, the host, the config and opening a browser stay midcode's to say, so `--port`, `--host`, `--config`, `--open` and `--strictPort` are dropped. The script is only read when it does nothing but call Vite: `a && vite`, or `FOO=1 vite`, isn't.

### What the plugin adds

The plugin runs on `.tsx` and `.jsx` files outside `node_modules` and adds attributes to what Vite serves. The rest of each file stays where it was, so line numbers stay true.

```tsx title="src/App.tsx"
import { Hero } from './components/Hero'

export default function App() {
  return (
    <main className="mx-auto max-w-5xl px-6">
      <Hero title="Ship the dashboard" />
    </main>
  )
}
```

```tsx title="src/components/Hero.tsx"
export function Hero({ title }: { title: string }) {
  return (
    <section className="py-24">
      <h1 className="text-5xl font-semibold">{title}</h1>
    </section>
  )
}
```

The `<section>` reaches the page like this:

```html
<section data-mc="src/components/Hero.tsx:3:5" data-mcu="Hero|src/App.tsx:6:7" class="py-24">
  <h1 data-mc="src/components/Hero.tsx:4:7" class="text-5xl font-semibold">Ship the dashboard</h1>
</section>
```

- `data-mc`: where the element is written, as file, line and column of its `<`.
- `data-mcu`: on the element a component returns, where that component is used. Nested components make a chain, outermost first.
- `data-mci`: the same `Hero|src/App.tsx:6:7`, handed to the component as a prop. It shows in the page only if the component passes its props on to an element.

### When midcode's way doesn't start

Some projects need what only their own script sets up: an env file loaded by a wrapper, another process, a toolchain that wraps Vite. If Vite started this way exits, or doesn't answer in 45 seconds, midcode stops it and runs your `dev` script instead (`npm run dev`, or the same with your package manager, in your shell), reading the address from its output.

The site then shows, but without marks, so nothing on the canvas can be edited. You notice in three places:

- a message when the canvas appears: "midcode couldn't start this project its own way, so it runs your dev script: the site shows, but editing on the canvas needs midcode's plugin. The log says why." It has a "Send report" button.
- the dev server log, which says what happened ("it stopped", or "no answer in 45 s") before the second start.
- the right panel: under Code, a selected element has no file and line.

The same happens from the start when midcode can't find `vite` among the project's installed packages but there's a `dev` script (a toolchain that wraps Vite).

## What you can edit

Select the `<h1>` above and change its size. One class is swapped:

```diff title="src/components/Hero.tsx"
-      <h1 className="text-5xl font-semibold">{title}</h1>
+      <h1 className="text-6xl font-semibold">{title}</h1>
```

Double-click it and type. The words aren't in `Hero.tsx`, so midcode looks for the exact text in the project and edits it where it's written:

```diff title="src/App.tsx"
-      <Hero title="Ship the dashboard" />
+      <Hero title="Ship the new dashboard" />
```

The rest of the editor works as it does everywhere JSX is edited:

- [Text, images and video](https://midcode.app/docs/editor/text-and-media.md). New media is copied into `public/`.
- [The style panel](https://midcode.app/docs/editor/styles.md), per breakpoint.
- [Insert](https://midcode.app/docs/editor/insert.md), and [moving, duplicating and deleting](https://midcode.app/docs/editor/select-move-resize.md). Interactive components are written once to `src/components/midcode/`.
- [Components](https://midcode.app/docs/editor/components.md) and [Variables](https://midcode.app/docs/editor/variables.md).
- [CMS](https://midcode.app/docs/data/cms.md), [Languages](https://midcode.app/docs/data/languages.md), [Code view](https://midcode.app/docs/editor/code.md), [Publish](https://midcode.app/docs/publish/publish.md).

## Pages

A Vite app has no convention for pages, and a router written in code (`createBrowserRouter`, `<Routes>`) isn't read. The page menu lists Home only. To see another page, open the menu, type its path (`/pricing`) and press `Enter`.

Routes kept as files are read in the next release: see [TanStack Start](https://midcode.app/docs/frameworks/tanstack-start.md), and Qwik City below. Those are also the Vite apps where midcode adds and removes pages, because a page there is a file. With a router written in code, a new page is yours or your agent's to write.

## Site settings (next release)

In the next release the globe in the top bar opens Site settings in a Vite project too. The title, the description, the language, search engines, the favicon and the social image are written as tags in the `<head>` of the `index.html` at the project's root:

```diff title="index.html"
-    <title>Vite + React</title>
+    <title>Oak Studio</title>
+    <meta name="description" content="Furniture made to last." />
   </head>
```

A tag that's there has its value changed, and a new one goes last in the `<head>`. Images are copied into `public/` as `favicon` and `og-image`, with the extension of the file you picked (`favicon.png`, `og-image.jpg`). A value Vite fills in when it builds (`%VITE_APP_TITLE%`) shows as code and isn't written. There's one form for the whole app: a page has no `<head>` of its own. It was tried on a Vite project with Vue, which has the same `index.html`. See [Site settings](https://midcode.app/docs/editor/site-settings.md).

## Styles

With Tailwind 4 (a CSS file with `@import "tailwindcss"` or an `@theme` block), midcode writes Tailwind's classes: see [Tailwind CSS](https://midcode.app/docs/styling/tailwind.md).

Without it, midcode writes `mid:` utilities and compiles them to `midcode.css`. On your first style edit the file is created next to the module your `index.html` loads, and imported there after its last import:

```diff title="src/main.tsx"
 import { createRoot } from 'react-dom/client'
 import './index.css'
 import App from './App.tsx'
+import './midcode.css'
```

If `index.html` loads no module, midcode looks for `src/main.*`, `src/index.*` or `src/entry-client.*`. If it finds none, it writes `midcode.css` anyway and tells you to import it. See [Without Tailwind](https://midcode.app/docs/styling/without-tailwind.md).

## Preact, Solid and Qwik (next release)

A Vite project with `preact`, `solid-js`, `@builder.io/qwik` or `@qwik.dev/core` in place of `react` is started and marked the same way: it's the same JSX and the same plugin. The released version already edits Preact and Solid projects this way. What the next release adds:

- These libraries write `class`, not `className`. A new attribute follows the file it goes into: when the file says `class=` and never `className=`, midcode writes `class`, and what you drag in from Insert is converted (`className` to `class`, `htmlFor` to `for`).
- midcode's own interactive components (Carousel, Tabs, Slideshow, Ticker and the others) are React. Without `react` in the project they're refused with "This component needs a React page".
- Qwik City only starts with `vite --mode ssr`, which is why the options of your `dev` script are kept. Its pages are listed: each folder of `src/routes` that has an `index.tsx`, `.jsx`, `.mdx` or `.md`. "New page" in the page menu makes such a folder with an `index.tsx` in it.

One thing to know: in a file that has no class attribute yet, midcode has nothing to follow and writes `className`.

Tried with a Vite + Solid and a Vite + Preact project made by `create-vite`, and with a Qwik City project. SolidStart has never been tried.

## Limits

- No free canvas in the released version: `F` inserts a frame in the page instead of drawing one on the empty canvas. The next release [brings it](https://midcode.app/docs/editor/free-canvas.md) to a Vite app with an `index.html` at its root.
- No new pages and no Site settings in the released version. In the next release Site settings needs an `index.html` at the project's root, and pages are added only where routes are files.
- In the next release, a `.midcode/server.json` in the project replaces midcode's own start with your command or address. The site then shows without marks, to view and comment: see [Any other stack](https://midcode.app/docs/frameworks/custom-server.md).
- Only `.tsx` and `.jsx` files are marked.
- Your `vite.config` has to be at the project root. A `--config` in your `dev` script isn't followed.
- The dev server listens on midcode's port, not the one in your config or script. An app that only works on one fixed port (an OAuth callback, an API that allows a single origin) won't get it.
- An image your code imports (`import logo from './logo.svg'`) isn't swapped from the canvas: replacing imported images in place is Next.js only. An image referenced by its path is replaced and copied into `public/`.
- An element drawn by a package has no mark, and one that's moved stays in its file and its function.
- A Laravel app that uses Vite is a different case: the canvas shows what PHP serves. See [Laravel](https://midcode.app/docs/frameworks/laravel.md).

## Troubleshooting

**The site shows but nothing can be edited.** midcode fell back to your `dev` script. Open the dev server log: the line before the second start says why. Usual causes are a config that reads something only your script sets, or a version of Vite midcode's config can't load. "Send report" sends the log and how the project is set up.

**"Vite is not in node_modules. Install the dependencies."** The project has no `dev` script either. Install with the button on the card.

**A style doesn't show.** Check that `midcode.css` is imported in your entry file.

More in [Troubleshooting](https://midcode.app/docs/reference/troubleshooting.md). In a workspace, see [Monorepos](https://midcode.app/docs/frameworks/monorepos.md).
