Proxy Mode
smo1 advanced 6 min read
ELI5
Normally, a short link sends you somewhere else — your browser sees the new address. Proxy mode is different: it is like looking through a periscope. You stay on the short link URL, but you are actually seeing content from another website. The edge worker fetches the destination for you, rewrites any internal links so they still go through the short URL, and streams the response back without revealing the real destination.
Technical Deep Dive
Redirect vs. Proxy
| Aspect | Redirect mode | Proxy mode |
|---|---|---|
| URL bar | Shows destination | Stays on smo1.io/:slug |
| Status code | 302 Found | 200 OK (streamed) |
| SEO | Destination indexed | Short URL indexed |
| Path preservation | N/A (new URL) | /slug/path → /path |
| Cookie scoping | Destination domain | smo1.io |
| Use case | Standard short links | White-label, hiding destination |
Proxy Sequence
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e8f4f8', 'primaryTextColor': '#2d3748', 'primaryBorderColor': '#90cdf4', 'lineColor': '#718096', 'secondaryColor': '#f0fff4', 'tertiaryColor': '#fefcbf'}}}%%sequenceDiagram autonumber actor U as User participant Z as zoomies-edge participant D as Destination Server
U->>Z: GET smo1.io/:slug/dashboard Z->>Z: Resolve link (KV / API) Z->>Z: Rewrite path: /dashboard Z->>Z: Set Host header to destination Z->>D: GET https://destination.com/dashboard D-->>Z: 200 OK + HTML/CSS/JS Z->>Z: Rewrite Location headers Z->>Z: Strip Set-Cookie domains Z-->>U: 200 OK (streamed) Note over U,Z: URL bar still shows smo1.io/:slug/dashboardPath Rewriting
Original request: smo1.io/myapp/dashboard?tab=settings
Rewritten request to destination:
- Path:
/dashboard?tab=settings(slug prefix removed) - Host:
destination.com - Headers: Original headers forwarded, except
Hostis replaced
Sub-path handling: ALL /:slug/* route in zoomies-edge catches any path under the slug and applies the same rewriting rules.
Header Manipulation
Outgoing (Worker → Destination):
Host→ destination hostnameX-Forwarded-For→ original client IPX-Forwarded-Proto→https- Original
User-Agent,Accept,Accept-Languagepreserved
Incoming (Destination → Worker → User):
Locationheaders rewritten:https://destination.com/login→https://smo1.io/myapp/loginSet-Cookiedomain stripped:domain=destination.com→ removed (cookie scopes tosmo1.io)Content-Encodingpreserved for streaming
redirect: manual
The Worker uses fetch(url, { redirect: 'manual' }) instead of the default 'follow':
const response = await fetch(destinationUrl, { method: request.method, headers: modifiedHeaders, body: request.body, redirect: 'manual', // Critical!});Why manual?
- If the destination returns a 302, the Workers runtime would normally follow it automatically
- This would bypass the Worker’s header rewriting and path preservation
- With
manual, the Worker receives the 302 response, rewrites theLocationheader, and returns it to the browser — maintaining the slug prefix
Cookie Domain Stripping
When a proxied destination sets a cookie:
Set-Cookie: session=abc123; Domain=destination.com; Path=/; SecureThe Worker strips the Domain attribute:
Set-Cookie: session=abc123; Path=/; SecureWithout a Domain attribute, the browser scopes the cookie to the current host (smo1.io), preventing the destination from seeing cookies intended for the proxy path.
Limitations
| Limitation | Reason |
|---|---|
| Relative asset URLs may break | The browser resolves /style.css against smo1.io, not the destination |
| Absolute URLs in JS may bypass proxy | Frontend routing in SPAs may navigate to destination.com directly |
| WebSocket connections are not proxied | Workers runtime does not support WebSocket proxying in this configuration |
| Large file uploads may timeout | Workers have a 100 MB response size limit and 30-second CPU limit |
Workaround for broken assets: The destination should use absolute URLs or be designed to work behind a reverse proxy.
Key Terms
- Reverse proxy → Server that fetches content from another server and presents it as its own
- Path rewriting → Removing the slug prefix so the destination receives the correct sub-path
- redirect: manual → Fetch option that prevents automatic HTTP redirect following
- Domain stripping → Removing the
Domainattribute fromSet-Cookieheaders to scope cookies to the proxy host - White-label → Presenting third-party content under your own domain and branding
Q&A
Q: Why would someone use proxy mode instead of redirect? A: To hide the destination URL (e.g., affiliate links, private documents), to maintain brand consistency (the user never leaves your domain), or to add a layer of access control (protection + proxy).
Q: Can proxy mode bypass CORS?
A: No. CORS is enforced by the browser based on the origin in the address bar (smo1.io). If the proxied content makes XHR/fetch requests to the destination, those are cross-origin and subject to CORS. The Worker does not magically disable browser security.
Q: What happens if the destination is down?
A: The Worker receives an error from fetch() and returns a 502 Bad Gateway or 504 Gateway Timeout to the user. The error page can be customised in the Worker.
Q: Does proxy mode support POST requests?
A: Yes. The ALL /:slug/* route forwards all HTTP methods (GET, POST, PUT, PATCH, DELETE) to the destination with the same body and headers.
Examples
Think of proxy mode like a personal shopper:
- You tell the shopper “Get me a jacket from Store X, but I do not want anyone to know I shop there” (hide destination)
- The shopper goes to Store X, picks up the jacket, and brings it back to you in an unmarked bag (slug URL)
- If Store X says “Go to our accessories department” (redirect), the shopper intercepts the directions and rewrites them so you still go through the shopper (Location header rewrite)
- If Store X gives you a loyalty card (Set-Cookie), the shopper removes the Store X branding from the card so it looks like a generic gift card (domain stripping)
- The shopper cannot help you if Store X’s clothes have “Made at Store X” labels sewn into every seam (absolute URLs in HTML/JS)
neighbors on the map
- Request Routing & Edge Resolution debugging why a slug returns 404 instead of redirecting
- Link Protection implementing password-protected links