Guide

Share AI built apps as link: why the URL is a space, not a page

Most articles on this topic stop at "copy the share button, paste the link." The thing none of them mention: the link mk0r hands you points at a URL space, not a single page. Your friend can deep-link any route, hit the API your AI invented, and drive the app with query strings, all on a hostname free of platform chrome. The reason fits in 47 lines.

M
Matthew Diakonov
8 min read

The premise everyone gets wrong

The default mental model for an AI app maker share link is that it points at one screen. You typed a sentence, the agent built a thing, the share button froze that thing into a URL. Tap the URL, see the thing. End of story.

That is true for some shapes of share link. A static export, where the builder bundles your app into HTML+JS and uploads to a CDN, is one screen. An iframe wrapper, where the link points at the builder's editor with the preview pane embedded, is one screen with editor chrome around it. A "view-only" mode, where the platform serves a read-only snapshot of your project, is one screen.

mk0r picks a different shape. The link is a wildcard subdomain that rewrites to a live development VM, and the rewrite preserves everything past the hostname. The result is not a page. It is a URL space, the way any normal website on the internet is a URL space. The recipient can navigate it, deep-link into it, hit endpoints inside it, drive it with query parameters. The same things they can do on any real site, on the link your AI just produced.

The 47-line file that decides this

The whole behavior lives in one Next.js middleware file at src/proxy.ts. It is 47 lines including imports and the export config. The interesting work is inside the proxy() function, which is exactly eleven lines. Below is the full body of that function, as it ships.

// VM IDs are 15-30 char lowercase alphanumeric strings.
const VM_ID_RE = /^[a-z0-9]{15,30}$/;

const KNOWN_APEX = ["mk0r.com", "staging.mk0r.com"];

export function proxy(request: NextRequest) {
  const vmId = extractVmId(request.headers.get("host") ?? "");
  if (!vmId) return NextResponse.next();

  const target = new URL(request.url);
  target.port = "";
  target.protocol = "https:";
  target.hostname = `3000-${vmId}.e2b.app`;

  return NextResponse.rewrite(target);
}

The line that does the heavy lifting is target.hostname = `3000-$${vmId}.e2b.app`. The line above it sets the port to empty. The line below it returns the rewrite. That is the whole transformation. Pathname stays, search stays, the HTTP method stays, the body stays. The only field that changes is the host the request is forwarded to.

The matcher at the bottom of the file is "/:path*", which means every path under the wildcard subdomain enters this function. There is no allowlist of routes. Every URL on <vmId>.mk0r.com is in the URL space.

The anchor fact

One file, eleven lines, no per-link database row.

src/proxy.ts is 47 lines total. The proxy() function (lines 36 to 46) is the entire mechanism. The vmId regex on line 15 (/^[a-z0-9]{15,30}$/) is the only validation. There is no DB lookup, no auth check, no cookie inspection, no per-link row anywhere. Provisioning the next link costs zero.

What happens when your friend opens a deep link

The interesting case is not the home page. It is what happens when your friend pastes /admin/users/42?tab=billing after the hostname. The diagram below traces the request, end to end, on a path the AI invented.

Friend opens <vmId>.mk0r.com/admin/users/42?tab=billing

FriendWildcard DNSmk0r middlewareE2B VMDNS lookup <vmId>.mk0r.comA 35.186.212.31 (LB IP)GET /admin/users/42?tab=billingextractVmId() validates against /^[a-z0-9]{15,30}$/rewrite hostname only, leave path+query aloneGET 3000-<vmId>.e2b.app/admin/users/42?tab=billing200 OK, AI-built admin page renders

Notice what does not happen. No auth check. No allowlist of paths. No rewrite of the path or query. The middleware does not even know what routes the AI built; it does not need to. Whatever your friend types after the slash gets handed to the Vite dev server inside the sandbox, which renders whichever route the AI made.

The three URL parts and what each one is doing

A useful exercise: take the URL apart. Each piece has a job, and only the first one is fixed by the platform. The rest belongs to whatever the AI built.

<vmId>.mk0r.com/route?param=value

1

Hostname

<vmId>.mk0r.com is the only part the proxy reads and rewrites. Validated against /^[a-z0-9]{15,30}$/.

2

Pathname

Whatever your friend types after the slash. Forwarded verbatim to the Vite dev server. The AI's routes live here.

3

Query string

Anything after the ?. Also forwarded verbatim. Useful for ?invite=abc, ?theme=dark, anything the route reads.

4

Method + body

GET, POST, PUT, anything. Body bytes pass through unchanged. So /api/whatever the agent invented is reachable.

What the proxy preserves, and what it does not

The list below is taken straight from the source. Every yes is a thing your friend can do on the link. Every no is a thing the recipient does not get.

Things that pass through

  • Pathname (target.pathname is never assigned)
  • Query string (target.search is never assigned)
  • HTTP method (rewrite is method-agnostic)
  • Request body (Next.js rewrite forwards bytes)
  • Most request headers (Next.js rewrites preserve them)
  • TLS, via the *.mk0r.com wildcard certificate

Things that do not pass through

  • HMR WebSockets (proxy.ts comment lines 9-11 says so explicitly)
  • Auth cookies (the proxy does not inspect any cookies)
  • Per-link access control (no allowlist of vmIds)
  • Editor chrome (no /preview/ path, no platform UI)

The first list is what makes the link a URL space. The second list is what makes the recipient experience feel like a normal website instead of a builder dashboard. Both lists are deliberate, and both are visible in the same 47 lines.

Sharing a single page versus sharing a URL space

The two screenshots below describe the experience. Same prompt to the same friend. Different shape of share link on each side.

What the recipient can do with the link

You copy a link. Your friend taps it. They see the home page. They try /settings out of curiosity. 404. They open dev-tools, the network tab shows requests going to the platform's own CDN, not anywhere they can poke. The app is read-only the way a YouTube video is read-only: you watch it, you cannot interact with it past whatever the home screen renders.

  • Only one route is reachable
  • API endpoints are not addressable from the link
  • Query strings are usually not honored
  • Cannot deep-link past the home view

A small story that makes this concrete

Earlier this week I built a tiny recipe browser as a test. I asked the agent for a home page that lists recipes, a /recipe/[id] route for the detail view, and an /api/recipes endpoint that returns the JSON. The agent finished in about ninety seconds. I copied the share link from the URL bar in the preview pane.

A friend opened the link, then asked me a question I had not considered: "can I share a specific recipe with someone?" I said yes, just paste the link plus /recipe/3. They tried it. It worked. They sent it to a third person. That person also opened the link, also navigated to /recipe/3 directly, never saw the home page.

A different friend, more technical, opened a terminal and ran curl <link>/api/recipes. They got JSON back. They asked if I had set up CORS. I had not. The AI had defaulted to permissive headers, so the third person could in principle have called my AI-built API from a totally different web page. None of this was anything I had explicitly designed. It was downstream of one fact: the proxy preserves path, query, and method.

That is the kind of behavior that does not show up in a marketing page about "share your AI app." You only notice it once the recipient pokes at the link in ways the original tutorial did not mention.

47

Every line of the proxy that decides this fits in one file. There is no second mechanism, no fallback, no special-case for shared links.

src/proxy.ts in the appmaker repo

What this costs you, honestly

Sharing a URL space is not free of trade-offs, and pretending it is would be dishonest.

The recipient is hitting your live dev server. If you push a broken change, the link goes broken too, on whatever device your friend has it open on. There is no "production version" that lags behind. The artifact is the development instance.

The link has no auth. Anyone who has the URL can reach every route and every API endpoint the AI built. The privacy boundary is the unguessability of the vmId, which is fine for sharing with friends and bad for anything that touches private data. If you need real auth, tell the agent to add it inside the app, or move the project through the publish flow at src/app/api/publish/route.ts, which maps a custom domain in front of the VM.

Cold starts exist. After roughly ten minutes of inactivity, the VM pauses. The next click on the link wakes it back up, which adds latency on the first request. Not a deal-breaker, just an expectation to set with whoever you share with.

Want to see a URL space in action?

Hop on a call and we will build a quick app together, copy the link, and poke at routes you did not know existed.

Frequently asked questions

When the AI builder hands me a share link, what is that link, exactly?

On mk0r the link is a wildcard subdomain of the form https://<vmId>.mk0r.com. There is no path, no query, no token. The vmId is a 15 to 30 character lowercase alphanumeric string (regex /^[a-z0-9]{15,30}$/, defined as VM_ID_RE on line 15 of src/proxy.ts). The link is computed off the request host the moment your sandbox boots, in src/app/api/chat/route.ts lines 244 to 247, and sent to the client in the first session event of the chat stream. There is no insert into a database, no minting step. The hostname IS the identifier.

Why is the link a URL space and not a single page?

Because the middleware that handles requests at <vmId>.mk0r.com only rewrites the hostname. The proxy() function in src/proxy.ts (lines 36 to 46) builds a target URL by setting target.protocol = 'https:', target.port = '', and target.hostname = `3000-${vmId}.e2b.app`. It never touches target.pathname or target.search, and it does not rewrite the HTTP method. The Next.js matcher is `/:path*`, so every path under the wildcard subdomain enters the same rewrite. The consequence: anything your friend types after the slash is forwarded verbatim to the dev server. A single share link gives them every route the AI invented.

What can my friend actually do with the URL space, in practice?

Three concrete things that a frozen demo page does not allow. First, deep-link routes: if the AI built a multi-page app, your friend can paste https://<vmId>.mk0r.com/settings or /admin/users/42 and land on that screen directly, no clicking through. Second, hit the API: if the agent built /api/recipes, your friend can curl that endpoint, paste it into Postman, or call it from another app. Third, drive the app with query strings: ?theme=dark, ?invite=abc, anything the route reads. The link behaves like a real public website, because the request is reaching a real Vite dev server with the AI-written routes intact.

What does the proxy NOT pass through?

Two things, both deliberate. HMR WebSockets do not pass through this proxy (the comment on lines 9 to 11 of src/proxy.ts is explicit: 'HMR WebSockets do NOT pass through this proxy, so the in-app dev iframe keeps using the direct VM URL for live-reload'). That means a recipient does not get a hot-reload connection to your dev server, only normal page reloads pick up changes. The other thing missing is auth: there is no cookie inspection, no session lookup, no allowlist. The privacy boundary is the unguessability of a 15 to 30 character vmId. If the recipient knows the URL, the recipient is in.

How is this different from sharing a Replit link or a CodeSandbox link?

Both of those wrap the running app inside their editor. The URL is something like replit.com/@user/project or codesandbox.io/p/sandbox/abc, the visitor first lands in the editor UI, has to find the preview pane, and the page is often unusable on a phone because the editor itself is not a mobile experience. mk0r's link is host-only, no editor chrome. Your friend's URL bar shows <vmId>.mk0r.com, the page renders the running app full-bleed, and the recipient can deep-link any route inside it. There is no platform UI between them and the app you built.

What happens if my friend opens the link tomorrow when nobody is using the VM?

The E2B sandbox is created with `{ onTimeout: 'pause', autoResume: true }`. After about 10 minutes of inactivity the VM is paused (RAM serialized, disk image kept), not destroyed. The vmId is persisted in Firestore under app_sessions, so the routing still has somewhere to send the request. When your friend taps the link, E2B sees the inbound request, wakes the paused sandbox, and the dev server picks up where it left off. The first request after a long pause has a cold start (sandbox wake plus Vite recompile of entry chunks); subsequent requests are normal latency. If you want it snappy, ping the URL yourself a minute before sharing.

Is the URL space safe to share with a stranger?

Treat it like any public URL on the internet. Anyone with the link can hit any route the AI built, including any API endpoints the agent invented. If your app reads from a database, anyone with the link can read what that database returns through your API. If your app writes to a database, anyone with the link can write. The pre-baked sandbox does not include secret keys for paid services, but anything the agent wrote is reachable. For a public toy or a demo with friends this is fine. If you need access control, the right move is to either tell the AI to add auth inside the app, or use the publish flow at src/app/api/publish/route.ts to map a custom domain in front of the VM and put a real edge in the way.

Does the share link change if I edit the app later?

No. The link is the hostname, the hostname is the vmId, and the vmId is one-per-session. As long as the session lives, the link is stable. Edits land into the same dev server through the agent, and the next page reload (or a normal F5 from your friend) picks them up. This is the inverse of a CDN-export demo link, where each redeploy cuts a new artifact and the old URL goes stale. mk0r's pattern privileges iteration: the same URL is the live version, all the way through.

What if the recipient is on a phone, on cell data, no platform account?

That is the case the design optimizes for. The proxy in src/proxy.ts has no auth check, so 'no platform account' is a non-issue. The Next.js middleware sits behind a Google Cloud HTTPS load balancer terminating the *.mk0r.com wildcard certificate, so cell data still resolves the hostname and TLS handshake works the same as any HTTPS site. The system prompt instructs the agent to design mobile-first, so the rendered app fits a phone viewport. There is no 'install the app' or 'sign up to view' modal. Tap, render, done.

What is the simplest way to verify a share link is a URL space and not just a single page?

Build a tiny app that has more than one route. Tell the agent: 'add a /about page that says hello'. Once the agent finishes, copy the share link, then in another browser open <copied-link>/about. If you see the about page directly, the link is a URL space. If you see the home page or a 404, you are holding a frozen single-page demo. mk0r's link passes this test because the proxy in src/proxy.ts forwards the path verbatim to the dev server. No marketing copy distinguishes the two. The route test is the honest answer.

mk0r.AI app builder
© 2026 mk0r. All rights reserved.