# Publish a framework website on Rogue

For Next.js-compatible server rendering, API routes, authentication or SQL, use
**[Vinext HTTP hosting](https://rogue.camp/guides/vinext.md)** and optional
**[project D1](https://rogue.camp/guides/databases.md)**. The workflow below covers
static output. Vinext apps deploy through `rog apps`, preserve HTTP responses and
run at a dedicated app origin; static releases use `rog-publish`.

Rogue detects static routes from the files in your release. No route list,
per-page DNS record, manual rewrite or refresh is required. Pro custom domains
serve your HTML and assets directly, without a Rogue wrapper or iframe.

## 1. Inspect before choosing a build

Read the project's instructions, package scripts and framework configuration.
Call `publishing_capabilities` for the account's current limits and prerequisites.
Use the existing customer account; keep credentials in private configuration.
Do not execute build scripts from an untrusted repository without review.

Choose based on the actual output, not the framework's name:

| Application | Publishable output | Routing choice |
| --- | --- | --- |
| Next.js static export (`output: 'export'`) | Complete generated `out/`, or the configured export directory | `spa:false`; both trailingSlash settings work |
| Astro, Hugo, Eleventy, Gatsby, other static generators | Complete configured static output directory | `spa:false` |
| Vite/React/Vue or another browser-only SPA | Built directory containing HTML, JS, CSS and assets | `spa:true` only if the app uses a history router |
| Next.js SSR, Server Actions, runtime API routes, or other server applications | Vinext `dist/server` and `dist/client` | `rog-vinext pack`, then `rog apps deploy`; see the Vinext guide |

Do not change a customer's Next.js configuration merely to fix clean URLs.
`about.html` and `about/index.html` are both supported automatically. Pre-rendered
dynamic routes work when their HTML files exist in the output. Runtime-only
routes do not become static by adding `spa:true`; use a supported runtime build
or explain the missing runtime capability before proceeding. Rogue does not
automatically build source, run arbitrary Node servers or implement framework
rewrites, redirects, ISR, image optimizers and server actions from an upload.

## 2. Build and inspect the complete output

Run the project's documented production build. Select the resulting directory
with `index.html` (or explicitly choose another HTML entrypoint). Include every
required file: nested HTML, `_next`/other framework chunks, CSS, fonts, images,
JSON and framework navigation payloads. Do not upload just the homepage, the
source directory, `.env`, `node_modules`, or a `.next` server build as a static site.
Respect the current path, MIME, file-count and byte limits returned by Rogue;
do not silently omit unsupported files or convert an error into success.

Create the manifest from the actual output bytes: relative `path`, `content_type`,
SHA-256 and byte `size` for every file. Keep the existing project's identity and
immutable Git `source_commit`. Use the official publisher or SDK to assemble
the upload; `import_git_publication_release` can instead import a prebuilt
directory already present at that exact Git commit. Import does not execute builds.

## 3. Preflight, upload, finalize, promote

1. Call `preflight_publication_release` with the complete prepare request.
   Inspect `valid`, `issues`, quotas and `routing.verification_paths`. Compare
   those paths with the app's navigation: an expected page missing from the
   manifest needs a corrected build/output selection, not a DNS edit.
2. Call `prepare_publication_release` once with a stable key. Save its release
   `id`, `publication_id`, request and `expected_release_id` separately.
3. Upload exact bytes to the returned REST upload URLs. After a timeout, call
   `get_publication_release` and resume only missing files with the same ID/key.
4. Call `finalize_publication_release` with the release ID. This checks assets
   and makes the release ready; it does **not** publish it.
5. Call `promote_publication_release` with the release ID and current
   `expected_release_id` (null only for the first publication). On a conflict,
   inspect the active release before retrying. Never replace the expectation
   with null to force an update.
6. Read back the publication/release and verify the promoted release is current.
   Existing custom-domain claims stay attached to the stable `publication_id`.
   A new release and its routes need no claim recreation or refresh.

## Automatic URL resolution

For `/foobar`, Rogue checks, in order:

1. The exact file `foobar`.
2. The HTML file `foobar.html`.
3. The HTML file `foobar/index.html`.
4. The entrypoint, only for extensionless navigation with `spa:true`.

`/` uses the configured entrypoint. `/foobar/` also resolves the HTML candidates.
On custom domains, directory indexes redirect `/foobar` to `/foobar/` so relative
assets such as `./image.png` resolve inside the directory. Flat HTML exports
keep extensionless URLs and redirect a trailing slash to that flat route. Exact JS, CSS, JSON and navigation payloads retain
their bytes and MIME type; a missing `.js`, `.css` or `.txt` stays 404 rather
than returning HTML. Unknown static pages stay 404 when `spa:false`.

## 4. Connect a domain once

Use `create_custom_domain` with the stable **publication ID**, not the release ID.
Follow [the domain guide](https://rogue.camp/guides/custom-domains.md) for DNS and
ownership/TLS validation. Both apex and www require their own claims if used.
Only change DNS when domain diagnostics identify a DNS problem.

Verified Pro custom domains serve the authored HTML, metadata and assets as a
normal top-level website. Links, history, native dialogs and browser storage
belong to that origin; the iframe-only `window.rogue` bridge is not injected.
Declare external `connect_hosts` where required. Hosting accepts GET/HEAD;
backend/MCP actions use their documented API URLs. This does not add form POST
handlers or arbitrary SSR support. Rogue-hosted UUID pages retain isolated
iframes on every plan because they share Rogue's authenticated origin. Free
owners also retain the Rogue banner; custom domains require Pro.

## 5. Verify what visitors actually receive

Use real GET requests, not only `curl -I`. Follow redirects and verify final
status, Content-Type and expected page content. Test `/`, each important nested
route (including with and without its trailing slash), and referenced JS/CSS,
images and framework navigation payloads. `routing.verification_paths` from
preflight/get-release gives the automatically detected HTML paths to test.

Open the site in a browser. Click internal links, reload a deep URL, use Back
and Forward, and check the console/network panel for failed chunks, CSP or MIME
errors. A 200 homepage is not proof that nested pages or hydration work.
On the custom domain, verify that the authored page is top-level with no Rogue
iframe. On a Rogue UUID URL, also inspect the iframe/release-host requests.

| Symptom | Next action |
| --- | --- |
| Pending DNS/ownership/TLS | Follow domain `next_steps`; refresh after the cooldown |
| Release ready but not current | Promote the existing release with the correct expected ID |
| Homepage works, nested page is 404 | Check expected HTML in the promoted manifest; compare exact `.html` and directory index URLs |
| Page loads but scripts/styles fail | Check complete output upload, asset MIME, paths and CSP; do not enable SPA fallback for missing assets |
| HTML files exist and exact files work but clean URLs fail | Report a Rogue route-resolution bug with both URLs and their GET responses |
| Runtime route fails on a static upload | Select a runtime deployment; uploading source does not execute it |

For support, supply hostname, stable publication ID, release ID, failing path,
status/content type, expected manifest file and timestamp. Include browser
errors if relevant. Never send bearer tokens, private keys, or environment files.
Do not call a routing failure "propagation" or claim a cache purge is needed
without evidence. Keep the working release and DNS intact during diagnosis.

## Fast static navigation and current access

Use the canonical exported URL in links: `/portfolio/` for `portfolio/index.html`,
or `/portfolio` for `portfolio.html`. Rogue resolves a necessary redirect from the
manifest before reading file bytes. Custom domains resolve their host, access and
manifest once and dispatch directly to static hosting or the server app.

Rogue caches immutable static bytes internally by release UUID and SHA-256, after
checking current publication access. Browser responses remain `private, no-store`:
unpublishing, privacy changes, owner suspension, custom-domain removal/plan expiry
and maintenance block subsequent requests even if old bytes remain cached. A
request already authorized may finish; bytes already downloaded cannot be recalled.
Promoting a release selects a new immutable key. Cached bytes contain no credentials
or injected visitor content. HEAD does not download an uncached body.

Project app URLs use `/username/project-slug`; UUID URLs remain valid. Follow the
returned publication `url`, project `app_url`, and manifest verification paths.
