---
title: "Embedded previews"
description: "Relay live previews through your own authenticated HTTP and WebSocket gateway."
canonical_url: "https://hoplite.sh/docs/platform/previews"
markdown_url: "https://hoplite.sh/docs/platform/previews.md"
---

# Embedded previews
URL: /docs/platform/previews
LLM index: /llms.txt
Description: Relay live previews through your own authenticated HTTP and WebSocket gateway.

# Embedded previews

Your dashboard embeds your gateway. Your gateway authenticates the customer and relays requests to Hoplite. Hoplite routes them to the sandbox and restores saved source when a runtime has expired.

```text
dashboard iframe → your authenticated gateway → Hoplite preview proxy → sandbox
```

## Start a preview

Call `POST /api/platform/v1/apps/{appId}/threads/{threadId}/preview/start` from your backend with `{"frameOrigin":"https://dashboard.example.com"}`. Use an exact HTTPS origin, with no path or trailing slash. Starting requires `thread:update`; the thread must already have a provisioned workspace. Configure its run command and preview port with the app’s `runScript` and `previewPort`.

The response contains `preview.status`, `preview.url`, and a `preview.gateway` credential when available. A starting preview may have no URL yet. Read `GET /api/platform/v1/apps/{appId}/threads/{threadId}/preview?frameOrigin=...` to inspect readiness and refresh access.

Starting the preview of an archived workspace restores its saved source into a new sandbox and runs `runScript` again, so any dependency installation in `runScript` repeats. A [sandbox image](/docs/platform#sandbox-images) with your dependencies preinstalled shortens that wait.

## Relay requests

Use a long-lived service that supports WebSocket upgrades. The gateway in Hoplite's [reference application](https://github.com/CarbonCopyInc/hoplite-platform-starter) demonstrates transport and token renewal; see `apps/studio/lib/preview-gateway.ts` there.

For each HTTP request and WebSocket upgrade:

1. Authenticate your user and verify membership in the organization that owns the site.
2. Resolve its Hoplite thread from your server-side mapping. Never accept an arbitrary upstream URL from the browser.
3. Forward the request to `preview.url`, adding the returned gateway token as `X-Hoplite-Preview-Token`. Strip any browser-supplied version of that header.
4. Preserve the request path, query, WebSocket protocols, and response content. Rewrite redirects and cookie domains where required. For upgrades, use the Hoplite preview origin as the upstream `Origin`.

Give each preview an isolated origin so root-relative assets and HMR resolve correctly. Keep service keys, gateway tokens, and upstream URLs on the server. Hoplite strips its access header before forwarding to generated code, restricts framing to your dashboard origin, and disables indexing and caching.

## Preview states

Requests that carry `X-Hoplite-Preview-Token` never receive Hoplite-branded pages. When Hoplite answers a request itself instead of relaying the application, the response carries an `X-Preview-State` header and a neutral body with no Hoplite name, logo or URLs:

| `X-Preview-State` | Status | When |
| --- | --- | --- |
| `starting` | `202` for documents, `425` otherwise, with `Retry-After` | The sandbox is booting or restoring saved source. The document refreshes itself every two seconds until the application answers. |
| `unavailable` | `503` for documents, `502` otherwise | The preview could not start. Call the start endpoint again, then retry. |
| `not_found` | `404` | The preview no longer exists. Read the preview again to get its current URL. |
| `unauthorized` | `401` | The gateway credential is invalid or expired. Refresh it through GET. |

WebSocket upgrade refusals carry the same header on their handshake response. A refused service-worker script gets `403` with `unavailable`.

Relayed application responses never carry `X-Preview-State`, including the application's own errors; Hoplite removes the header if generated code sets it. Hoplite routes to the last known sandbox while it re-checks the route. If that sandbox has stopped, its provider's error is never relayed: Hoplite waits up to five seconds for the re-check and answers from the current sandbox, or reports `starting` while it restores the preview. Requests with a body wait for the re-check before they are sent.

To show your own page, check for `X-Preview-State` on each response and replace the body. For `starting` documents, render your page and retry after `Retry-After` seconds.

## Renew access and record activity

Gateway credentials last five minutes. Refresh them through GET; an idempotent start replay returns the original credential, which may have expired. Open WebSockets close within five seconds of the next authorization check after expiry, so reconnect with refreshed access.

Revoking a service key prevents new grants but does not revoke an existing grant before expiry. Your gateway must immediately reject users whose membership is removed.

Preview polling does not count as editor activity. Call the [workspace activity endpoint](/docs/api/recordPlatformWorkspaceActivity) while a user is actively editing to renew the sandbox idle deadline. Starting a preview does not publish a release or perform release QA.

## Sitemap

Sitemap discovery is not enabled for this deployment.
