One Remix route holds the markup and the head — edit both
A Remix route is one file holding both the markup and its head values, which is exactly the shape a visual editor wants. PortBay writes your element edits into the route's JSX and your title and description into the same file's meta export. Pro, early access, opt-in.
How do I visually edit a Remix route?
A Remix route module is unusually convenient for visual editing, because one file holds both the markup a page renders and the meta export that supplies its title and description. The hard part is linking a rendered element back to its line in that file. PortBay does it by adding a source-location plugin to the Vite dev server it launches for you, which needs nothing installed in your project — a Remix app on Vite, including the react-router scaffold that succeeded it, already exposes the config seam that requires. Click an element, change its text or className, and the edit is spliced into the route module through a tree-sitter path rather than a text replacement. Title and description edits land in the same route's meta array. The editor is Pro, in early access behind a Settings opt-in, and absent from the open-source build.
Verified against the product, 2026-09-05.
A Remix route, markup and head
PortBay runs your Remix project at its own .test URL with trusted HTTPS and dispatches board cards into it. The visual editor edits that same running app in your real browser — one live route, not a multi-frame design canvas.
Your first route-module edit
Four steps. If your Remix app runs on Vite — which every current scaffold does — none of them installs anything.
- Install PortBay and add your Remix project
- Turn the visual editor on
- Start the project — the stamping rides your own dev server
- Open Edit in browser, navigate to a route, change it
Install PortBay and add your Remix project
Homebrew installs the app and the bundled portbay CLI. Add the project folder and PortBay recognises Remix from an @remix-run dependency, runs your dev server, and serves it at its own .test hostname over trusted HTTPS.
brew tap portbay-app/portbay && brew install --cask portbayTurn the visual editor on
It ships to Pro under early access and is off by default. Sign in to Pro, then flip the Early Access toggle. The open-source build does not include the editor at all — it is not compiled in.
Settings › Account › Early AccessStart the project — the stamping rides your own dev server
Because PortBay launches the dev server, it passes Vite a generated config that imports and spreads yours and adds one plugin. The config lives in PortBay's data directory, never in your repo, and the plugin applies only while serving, so a production build never loads it.
portbay start myappOpen Edit in browser, navigate to a route, change it
The project's Visual editing section has one button. It opens your normal browser at your normal .test URL with the edit bar docked at the top, booting in Preview mode where links and loaders still work — so you navigate to the route you want the ordinary way, then switch to Edit. The rail also edits Tailwind design tokens, and a token rename rewrites every class referencing it across the project in the same save.
https://myapp.test/aboutOne route file, two kinds of edit
Remix keeps a page's markup and its head values side by side. PortBay writes into both, through different paths, and it is worth seeing which is which.
An absent field is inserted rather than refused: ask for an og:image on a route whose meta array has none and a descriptor is added to that array, in that file, for that route only.
// app/routes/about.tsx export const meta = () => [ { title: "About" }, // <- Page settings { name: "description", content: "About us" }, // writes here]; export default function About() { return ( <main> <h1 className="hero">We build boats</h1> {/* <- element edits */} <p className="lede">Since 1994, in one shed.</p> {/* write here */} </main> );} // The meta export is also recognised as:// export function meta() { return [ ... ]; }// export const meta: MetaFunction = () => { return [ ... ]; }//// Routes are resolved through the flat-dotted convention, so// app/routes/boats.$id.tsx is the file behind /boats/42.
The pieces underneath
Visual editor
Click an element on your running site in your own browser and edit its text, classes and styles straight into the source file, across 21 stacks.
Explore →Agent dev environment
Stack detection, runtimes, HTTPS, databases and mail, provisioned per project — the running app your agents verify their work against.
Explore →Asked before downloading
By any dependency whose name starts with @remix-run/ in your package.json. That covers every package in the family rather than one specific entry point, so a project that depends on @remix-run/node and @remix-run/react but not on a meta-package is still recognised.
Not if your Remix app runs on Vite, which every current scaffold does. PortBay launches the dev server, so it hands Vite a generated config that imports and spreads your own and adds a source-location plugin carried inside the app itself. Your package.json, lockfile and vite.config stay byte-identical, and there is no package to add or remove.
An older Remix app drops to the text-matching lane. A Remix project without React and Vite in package.json gives PortBay no dev-server seam to instrument, so text, class and attribute edits resolve by searching your source instead of by following a coordinate, and structural edits are refused rather than guessed. Moving that project onto Vite is what turns precise mapping back on — there is nothing to install either way.
Into that route's own meta export, in the same file as the markup you were just editing. Remix renders a route's meta into exactly that route's head and nothing else's, so the value cannot leak onto another page. PortBay reads the array whether it is written as an arrow function, a concise arrow body or a function declaration, and if a field is missing it adds a descriptor to the array rather than declining.
Component usages. Route modules are .tsx, which is a script-first file, so the host elements you wrote carry coordinates but a <Card /> tag does not. The practical consequence is that markup produced by a component you did not author — a UI library's own internals — has no coordinate. The reason is specific to the file type: in a .tsx a capitalised tag can be a TypeScript generic parameter list, and inserting an attribute into the wrong one would be a syntax error in your module.
Both. A className edit on a .tsx goes through a tree-sitter path that rewrites the attribute in place rather than a text replacement, and the right-hand rail covers layout, spacing, size, position, typography, backgrounds, borders and effects. Text is edited by double-clicking it in the live page rather than typing into a panel field.
No. It is a Pro feature, still in early access behind an opt-in toggle in Settings, and it is not compiled into the open-source build. It also refuses to run while the project is shared over a public tunnel, and the edit bar works only on local .test hosts. Running the Remix dev server on a real .test domain with trusted HTTPS is in the free app.
Yes. JSX and TSX are among the four formats where that structural edit is supported, so an element inside a route module can be moved into a new component file, with the import added to the route and the original span replaced by the tag. It refuses, by name, any span carrying a template expression — a value that was a local in the route would not exist in the new file, and the extraction would produce a module that no longer builds.

Give your projects and your agents a real local home.
Download for macOSFree & open source · macOS 11+ on Apple Silicon · Pro from $10/mo