Skip to content
PortBay

SvelteKit routes, edited in the +page.svelte that rendered them

Every element in a .svelte file reaches the browser carrying its file, line and column, added by the dev server PortBay launches and never written back to your files. Edits land in the +page.svelte that rendered them, and a page title lands in that route's own svelte:head. Pro, early access, opt-in.

The short answer

How do I visually edit a SvelteKit page?

The trap in editing a SvelteKit site visually is not finding the markup — it is writing to the right file. A route renders through a site-wide shell, so a tool that resolves changes to src/app.html will happily set what looks like this page's title and quietly set every page's title instead. PortBay resolves the route first. Markup edits go to the src/routes/**/+page.svelte that actually rendered the element you clicked, because every element in a .svelte file carries its file, line and column while the dev server serves it. Page settings go to that same route's own svelte:head block, which SvelteKit hoists into the document head for exactly the routes that render it; the shell is the fallback only when no route file can be resolved at all. Pro, early access, opt-in under Settings.

Verified against the product, 2026-09-05.

In the app

A SvelteKit route and its head

acme-shop · Board6 cards3 agents online
Pending2
infra
Rate-limit the API
P1
Dark mode polish
P2
Todo2
auth
Add OAuth login
P0Claude Code0/3oauth-login
Auto-dispatching
implement OAuth on app.test
db
Migrate to Postgres
P1
In Progress1
Fix flaky checkout test
P0
Done2
Set up Mailpit inbox
P2
Seed demo data
P2
1/5 · AssignA card lands in Todo and @claude is mentioned

PortBay runs your SvelteKit dev server 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 page at one breakpoint, not a multi-frame design canvas.

Walkthrough

Your first route edit

Four steps from a SvelteKit project to a change committed in a +page.svelte. None of them adds a dependency.

  1. Install PortBay and add your Svelte project
  2. Turn the visual editor on
  3. Start the project — the routes are already mapped
  4. Open Edit in browser and change a route

Install PortBay and add your Svelte project

Homebrew installs the app and the bundled portbay CLI. Add the project folder; PortBay reads your package.json, recognises SvelteKit, runs your dev server and serves it at its own .test hostname over trusted HTTPS.

brew tap portbay-app/portbay && brew install --cask portbay

Turn 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 contain the editor at all — it is not compiled in.

Settings › Account › Early Access

Start the project — the routes are already mapped

PortBay writes a vite.config.mjs into its own data directory that imports and spreads your config and adds its plugin, then starts the dev server with one --config flag. Nothing is written inside your project, and the plugin applies while serving only, so a production build never loads it.

portbay start myapp

Open Edit in browser and change a route

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 the page stays fully interactive. Navigate to the route you want, switch to Edit, and the edits resolve to that route's file. Pulling a section out into its own component file is a single edit here — Svelte is one of the four stacks where extraction is supported.

https://myapp.test/about
Without PortBay

One file, three kinds of block, and one head

A .svelte file has no frontmatter fence and no template wrapper — the whole document is markup, with script and style as blocks inside it. That shape is why the head block is where a route's page settings genuinely belong.

The head values are writable when they are plain string literals, reached directly or through exactly one hop to a const or let in the script block. A title assembled from a substitution or returned by a function is refused by name, because a partial rewrite of a composed string is the kind of change that looks saved and renders wrong. The same standard governs the Tailwind lane: renaming a design token rewrites every class reference to it across your markup in one save, and refuses outright if it could not read the whole project rather than leaving half your files pointing at a name that no longer exists.

Questions

Asked before downloading

Routes are the case it is built around. The cross-stack parity test drives a real SvelteKit fixture — a src/routes/about/+page.svelte served at /about — and all four write lanes land in that file. The fixture deliberately ships the src/app.html shell alongside it, because resolving a route edit to the shell instead of the route is the specific failure the route-first ordering exists to prevent. Plain Svelte components under src/lib are stamped the same way and edit the same way.

Into that route's own svelte:head block, creating one if the route has none. SvelteKit hoists a svelte:head into the document head for exactly the routes that render it, so for a +page.svelte that is precisely the one route it resolves to — which makes it the only place a per-page value can honestly go. src/app.html is the site-wide shell and stays the fallback for a route whose file cannot be resolved at all. Build output such as .svelte-kit is refused as a write target outright, since the next build erases it.

Text from a rune, a store or a prop is refused, by name. PortBay reads your source, not Svelte's runtime semantics, so an h1 whose contents are an expression is a text run holding braces — and rewriting those bytes would either destroy the binding or write a literal where a value belongs. The refusal says it is a template expression and points you at the source. Static copy in the same file edits normally, so in practice you edit the words you wrote and change the data for the words you computed.

Yes. A .svelte file is markup-first — the whole document is one markup region, with script and style as blocks inside it — and in markup a capitalised tag is a component and nothing else. So the usage site is stamped safely, and an element that Team rendered still traces back to the line where you used it. A .jsx or .tsx file does not get that tier, because the same tag shape there can be a TypeScript generic parameter list. It is the clearest concrete advantage the Svelte and Vue lanes have over the React one.

Every rendered copy carries the same coordinate, because they all came from the same line of your template. So editing one row edits the template, and therefore every row — which is usually what you meant, and when it is not, the thing to change is the data. The case that genuinely gets hard is an unstamped project, where PortBay has to fall back to a unique id or to counting same-tag siblings on the page; a loop makes those counts disagree with the source and PortBay hands you a picker instead of guessing which occurrence you meant.

No. The stamping plugin is compiled into the PortBay binary, written out to PortBay's own data directory, and handed to your dev server through a config generated outside your repository. There is no package to add and none to remove: uninstall PortBay and your package.json, lockfile and node_modules are byte-for-byte what they were. It is not on npm and is not meant to be, because nothing needs to serve it — which is also why you will never see an install command for it on this site.

No to both, and both are worth being plain about. 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 at all. And it is one live page in your real browser at one breakpoint at a time — the breakpoint control resizes that single canvas rather than opening a wall of frames. If a multi-frame infinite canvas is what you are shopping for, this is not that. Everything else on this page — the .test hostname, trusted HTTPS, the dev server, the task board — is in the free, open-source app.

PortBay mascot — a friendly blue tugboat

Give your projects and your agents a real local home.

Download for macOS

Free & open source · macOS 11+ on Apple Silicon · Pro from $10/mo