When an HTML presentation works locally but fails after publishing, freeze the deployed artifact and inspect the first failed browser request or policy violation. Missing files, wrong base paths, blocked runtimes, CSP mismatches, and a nested ZIP entry point require different fixes. Repair the artifact, then retest the unchanged hosted URL in a clean browser.
This guide owns failure recovery after a build or upload. The HTML presentation hosting guide owns the category decision, and the preflight checklist owns prevention before release. Keeping those jobs separate avoids a troubleshooting page that merely repeats a publishing tutorial.
The five cases below are not hypothetical. Slidesfly ships the reviewable fixture sources and a machine-readable observation record. Three pairs were served over local HTTP and opened in Chrome; two pairs were passed through the current product validators. No public deployment, account mutation, viewer event, or third-party request was created.
Diagnose before changing the deck
“The deck is broken” is not yet a diagnosis. Freeze the deployed artifact before rebuilding it so that each observation refers to one known input. If possible, record its byte count and SHA-256, the host path, browser version, viewport, and whether the viewer is signed in.
Open the hosted URL in a clean browser profile, then use DevTools:
- In Network, enable “Disable cache” and reload once.
- Sort by status and inspect the first failed request, not the longest list of downstream failures.
- In Console, preserve the first CSP, JavaScript, mixed-content, or module-loading error.
- In Elements, inspect the broken node and the URL the browser actually resolved.
- Compare that URL with the file tree inside the exact uploaded artifact.
| Visible symptom | First evidence to capture | Likely branch | Do not infer yet |
|---|---|---|---|
| Slides load but images or fonts are blank | Failed request URL, status, initiator | Missing asset or wrong path | That the host stripped the file |
| Page shell appears but controls do nothing | First console error and script request | CSP or runtime loading | That all JavaScript is unsupported |
| Upload is rejected | Exact error code and message | Packaging or policy | That the deck would fail in a browser |
| Upload succeeds but reader is blank | Entry-point response, content type, console | ZIP root, build output, or runtime | That a 200 response proves the deck |
| It works only on the creator's machine | Request host, authentication, local path | Private CDN, dev server, or missing file | That republishing unchanged bytes will help |
The shortest useful incident record is:
| Field | Example |
|---|---|
| Artifact identity | deck.zip, 841,223 bytes, SHA-256 … |
| Hosted identity | Reader URL or static path plus deployment/version ID |
| First symptom | “Chart on slide 4 is blank” |
| First failed request | /assets/chart.svg → 404 |
| First policy error | Exact console text, or none observed |
| Repair hypothesis | “Root-relative asset escaped the deck subdirectory” |
| Verification | Same URL, clean profile, navigation and affected slide passed |
This separation matters because a 404 is transport evidence, a CSP error is browser-policy
evidence, and an upload rejection is host-policy evidence. They demand different fixes.
Reproduce the five failures
The corpus deliberately uses tiny files and visible text states. That removes presentation design, network timing, and framework complexity from the first diagnostic step.
| Case | Frozen failure | Repair | Observed failing state | Observed fixed state |
|---|---|---|---|---|
| Missing asset | HTML requests ./assets/chart.svg; package omits it | Include the referenced file | Request 404; asset-error | Request 200; asset-loaded |
| Wrong base path | Deck at /deck/ requests /assets/chart.svg | Request ./assets/chart.svg | Root request 404; base-path-error | Deck-relative request 200; base-path-loaded |
| Blocked remote runtime | Single-file HTML loads a remote script | Inline the required runtime | Upload validator returns MALICIOUS_CONTENT | Validator returns ok: true |
| CSP mismatch | script-src 'self' meets inline JavaScript | Move code to same-origin runtime.js | script-blocked | Runtime 200; script-ran |
| Nested ZIP root | Archive contains dist/index.html | Zip the contents of dist/ | Exact entry-point validation error | Extracted index.html and runtime.js |
Chrome 151.0.7922.170 recorded the browser cases through Python's static HTTP server on
127.0.0.1:3142. The remote-runtime and ZIP cases call scanHtmlUpload and extractDeckZip
directly in the repository test. The record includes fixture hashes so another reviewer can confirm
that the files did not change between observation and publication.
This is a diagnostic corpus, not a speed benchmark, security certification, or universal framework test. Its value is causal isolation: each failing and fixed pair changes one relevant condition.
Fix missing assets
A browser cannot load a file that was never deployed. This sounds obvious, yet local preview tools often serve source directories, public folders, or development dependencies that are absent from the final artifact.
In the failing fixture, index.html requests ./assets/chart.svg, but the package contains only
index.html. Chrome requests the correct deck-relative path, receives 404, and changes the visible
state to asset-error. The HTML itself is not repaired; the fixed package adds the expected SVG at
assets/chart.svg, producing 200 and asset-loaded.
How do I prove an asset is missing?
- Copy the full failed request URL from Network.
- Open the frozen ZIP or build directory and search for that exact path with case preserved.
- Check whether the build copied the asset, renamed it with a hash, or excluded it.
- Confirm the response is a real
404, not an HTML fallback returned with200.
| Finding | Correct repair | Misleading repair |
|---|---|---|
| File absent from final build | Fix the build/copy rule and rebuild | Add a random production redirect |
| Filename case differs | Make source and reference case-consistent | Rely on a case-insensitive laptop filesystem |
| Hashed filename changed | Let the build rewrite the HTML reference | Hard-code the previous hash |
| URL returns app fallback HTML | Correct static route/fallback behavior | Treat 200 as proof the image loaded |
Do not upload an entire source repository to make one missing file appear. Produce a minimal static artifact, review its contents, and run the framework compatibility matrix for the artifact shape you use.
Fix a wrong base path
Missing and misaddressed are different failures. In the base-path fixture, the asset exists inside
the deck package in both versions. The failing HTML uses /assets/chart.svg, which tells the browser
to start at the site origin. Because the deck lives under /base/failing/deck/, that request escapes
the deck directory and returns 404.
The fixed HTML uses ./assets/chart.svg. The browser resolves it relative to the document directory
and requests /base/fixed/deck/assets/chart.svg, which returns 200. MDN's
URL resolution guide
distinguishes current-directory references such as ./asset.js from root-relative references that
begin with /. A document <base> element
can change that resolution, so inspect it before replacing URLs mechanically.
When should I change the build base instead of the HTML?
If the artifact comes from a framework, fix the framework's public-base setting and rebuild. Manual
search-and-replace can miss CSS url(...), dynamic imports, source maps, worker URLs, or generated
chunks.
- Slidev documents
slidev build --base /subpath/for deployments below an origin path in its official hosting guide. - Reveal.js expects its required
dist, plugin, theme, and media paths to remain coherent; its installation guide also explains when features require a web server rather than direct file opening.
Use the Slidesfly Slidev hosting guide or Reveal.js sharing guide for the exact build shapes. This page is about identifying the failure, not duplicating those build tutorials.
Fix a blocked remote runtime
A deck can work locally because its browser can reach a CDN, private registry, development server, or authenticated asset host. The published viewer may be offline from that dependency, blocked by CSP, or subject to an upload policy that rejects executable remote dependencies.
The corpus uses a single-file HTML document with
<script src="https://cdn.example.invalid/runtime.js">. The current Slidesfly single-file scanner
rejects that source with MALICIOUS_CONTENT and the detail blocked keyword: <script src=. The
fixed document inlines the tiny runtime and passes the same scanner with no flags.
Should I always inline every dependency?
No. Match the repair to the artifact contract:
| Artifact contract | Runtime repair |
|---|---|
| Self-contained single HTML file | Bundle or inline the runtime, styles, and required data |
| Supported multi-file static bundle | Package relative local scripts with the entry point |
| General static website project | Keep build-generated chunks and configure routes/headers correctly |
| Application requiring secrets or a backend | Deploy it as an application, not as a presentation artifact |
Slidesfly's current multi-file path allows relative local scripts but rejects remote script sources;
the single-file path rejects <script src> entirely. Another host may have a different policy, so
preserve its exact error instead of generalizing this result to “browsers cannot load scripts.”
Bundling is not a license to include secrets. API keys, cookies, internal URLs, speaker notes, and private datasets remain readable in a published client artifact. Run the preflight checklist before retrying the upload.
Fix a Content Security Policy mismatch
Content Security Policy controls which resources a document may execute or load. The corpus applies
default-src 'self'; script-src 'self' and includes an inline script. The policy allows scripts from
the same origin, but not that inline block, so the visible state remains script-blocked.
The fixed package keeps the policy unchanged and moves the code into a local runtime.js. Chrome
requests it with 200, executes it, and displays script-ran. MDN's
script-src reference
documents source expressions, nonces, and hashes, while its
CSP guide explains why inline code is
blocked under a restrictive script policy unless explicitly authorized.
Why not add unsafe-inline to make the deck work?
Do not repair this by adding unsafe-inline without a deliberate security review. It broadly
weakens the script policy and can turn unintended inline markup into executable code. Prefer one of
these controlled paths:
- move first-party code into packaged same-origin files when the host supports multi-file decks;
- use a build that emits CSP-compatible hashes or nonces when the serving layer can supply them;
- for a deliberately self-contained artifact, make the code and host policy agree before upload;
- if the product's CSP is fixed, adapt the artifact rather than overriding the product boundary.
Also distinguish the deck's own meta policy from response headers applied by the host or reader. DevTools shows the violated directive and blocked resource; copy that text into the incident record. The untrusted HTML safety guide explains why CSP is only one layer alongside separate origins, iframe sandboxing, storage controls, and lifecycle enforcement.
Fix a nested ZIP entry point
A common packaging error is zipping the build directory rather than its contents. The failing tree looks plausible to a person:
deck.zip
└── dist
├── index.html
└── runtime.js
But a publisher that defines the archive root as the deck root cannot find /index.html. The
current Slidesfly extractor returns the exact error archive must contain index.html at its root.
The fixed archive contains:
deck.zip
├── index.html
└── runtime.js
The repository test constructs both archives in memory from the same HTML and runtime bytes. The
nested version fails; the rooted version extracts index.html and runtime.js.
How should I zip a framework build?
Build first, inspect the output, then archive the contents of the output directory. Do not rename a
source file to index.html, and do not assume the framework source directory is deployable. Check
for an exact lowercase root entry, required chunks, assets, themes, and plugins. On a case-sensitive
host, Index.html is a different path.
For Slidesfly, use the applicable product Guide and current plan/file limits rather than copying a command from an unrelated host. The quickstart documents the current upload path; the framework matrix records tested package shapes and recovery notes.
Verify the repair
Republishing is not verification. After the smallest causal repair, keep the viewer-facing identity stable when the product supports an update, then test as a recipient in a clean session.
| Gate | What to check | Evidence to retain |
|---|---|---|
| Artifact | Byte count/hash changed only as intended; file tree complete | Build ID, hash, tree or manifest |
| Transport | Entry point, affected assets, scripts, fonts, and media return expected status/type | Network export or named request/status list |
| Browser | No new first-party console failures; affected control now works | Browser/version, steps, observed state |
| Presentation | First/last slide, keyboard, touch, links, charts, fullscreen, mobile | Explicit pass/fail checklist |
| Reader policy | Correct visibility, expiry, password/allowlist, download/embed behavior | Clean-session observation, not owner session |
| Lifecycle | Update preserved the intended URL/version; rollback path known | Deck/deployment ID and current version |
| Measurement | Test traffic is labeled or excluded; real recipient evidence remains separate | Analytics note with viewer classification |
Use the publishing Proof Pack for a source-to-reader verification pattern. If the repair replaces an owned Slidesfly deck, follow the same-URL update workflow rather than creating a second ambiguous link.
A successful upload does not prove that the complete deck works. A 200 entry point does not prove
assets loaded. A browser pass does not prove access policy. A page view does not prove a qualified
recipient or business value. Preserve those evidence levels in the release record.
Frequently asked questions
Why does my HTML presentation work locally but not online?
Local preview may expose source folders, cached files, authenticated CDNs, development servers, or a different base URL. Inspect the first failed production request and compare it with the frozen build tree. Do not assume the host changed the HTML until the bytes or response policy prove it.
Why are images missing after I upload the HTML file?
The image may be absent, mis-cased, outside the package, or referenced through a path that resolves somewhere else. A single HTML file does not automatically contain nearby images. Embed them or ship a supported bundle with the exact relative tree.
Should asset URLs start with a slash?
Only when you intentionally mean the site origin root. For a deck deployed below a path, ./assets/
usually stays with the deck while /assets/ escapes to the origin root. Framework output should be
rebuilt with the intended base rather than edited blindly.
Why is JavaScript blocked after publishing?
Check whether the upload policy rejected the artifact, the script request failed, or CSP blocked execution. Those are separate layers. Copy the exact validator or console message, then adapt the artifact to the documented host contract.
Can I fix CSP by allowing unsafe-inline?
Technically it can make some inline scripts execute, but it weakens the protection substantially. Prefer packaged same-origin scripts, hashes/nonces under a controlled serving layer, or a build that matches the host's policy. Review the whole untrusted-content architecture, not one directive.
Why does my ZIP upload say index.html is missing?
Open the archive and inspect its first level. If the tree starts with dist/index.html, zip the
contents of dist/ so the archive root contains index.html. Also verify exact filename case and
that you uploaded build output rather than project source.
Does a successful upload mean the presentation is fixed?
No. Upload success proves only that the artifact passed that ingestion step. Reopen the hosted URL in a clean browser and verify requests, console, navigation, affected media, narrow viewport, and sharing policy.
Should I create a new URL after every repair?
Usually not when the hosting model supports versioned updates. Keeping the intended reader URL reduces recipient confusion, but verify that the update actually changed the current artifact and that rollback or version history behaves as documented.
When should I move the deck to a general static host?
Use a general static host when the artifact needs project-level routes, headers, CI previews, functions, custom infrastructure, or website integration. The same-deck hosting comparison explains that trade-off without claiming one model wins every case.
Once the artifact passes this recovery flow, publish or update it through the HTML presentation publishing guide. If it still depends on a backend, secrets, or custom application policy, stop treating it as a portable deck and deploy it as the application it has become.
Continue with the Quickstart or browse all HTML presentation publishing guides.