HTML presentation troubleshooting

HTML presentation not working after publishing: five reproducible fixes

Diagnose an HTML presentation that breaks after publishing with five reproducible failure-and-fix cases for assets, base paths, runtimes, CSP, and ZIP roots.

Author
Slidesfly
Reviewed by
Slidesfly product team
Published
Updated
Review method
Replayed a five-fixture local failure corpus in Chrome 151, current Slidesfly upload validators, and official MDN, Reveal.js, and Slidev documentation on 2026-08-24.

13 min read

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:

  1. In Network, enable “Disable cache” and reload once.
  2. Sort by status and inspect the first failed request, not the longest list of downstream failures.
  3. In Console, preserve the first CSP, JavaScript, mixed-content, or module-loading error.
  4. In Elements, inspect the broken node and the URL the browser actually resolved.
  5. Compare that URL with the file tree inside the exact uploaded artifact.
Visible symptomFirst evidence to captureLikely branchDo not infer yet
Slides load but images or fonts are blankFailed request URL, status, initiatorMissing asset or wrong pathThat the host stripped the file
Page shell appears but controls do nothingFirst console error and script requestCSP or runtime loadingThat all JavaScript is unsupported
Upload is rejectedExact error code and messagePackaging or policyThat the deck would fail in a browser
Upload succeeds but reader is blankEntry-point response, content type, consoleZIP root, build output, or runtimeThat a 200 response proves the deck
It works only on the creator's machineRequest host, authentication, local pathPrivate CDN, dev server, or missing fileThat republishing unchanged bytes will help

The shortest useful incident record is:

FieldExample
Artifact identitydeck.zip, 841,223 bytes, SHA-256
Hosted identityReader URL or static path plus deployment/version ID
First symptom“Chart on slide 4 is blank”
First failed request/assets/chart.svg404
First policy errorExact console text, or none observed
Repair hypothesis“Root-relative asset escaped the deck subdirectory”
VerificationSame 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.

CaseFrozen failureRepairObserved failing stateObserved fixed state
Missing assetHTML requests ./assets/chart.svg; package omits itInclude the referenced fileRequest 404; asset-errorRequest 200; asset-loaded
Wrong base pathDeck at /deck/ requests /assets/chart.svgRequest ./assets/chart.svgRoot request 404; base-path-errorDeck-relative request 200; base-path-loaded
Blocked remote runtimeSingle-file HTML loads a remote scriptInline the required runtimeUpload validator returns MALICIOUS_CONTENTValidator returns ok: true
CSP mismatchscript-src 'self' meets inline JavaScriptMove code to same-origin runtime.jsscript-blockedRuntime 200; script-ran
Nested ZIP rootArchive contains dist/index.htmlZip the contents of dist/Exact entry-point validation errorExtracted 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?

  1. Copy the full failed request URL from Network.
  2. Open the frozen ZIP or build directory and search for that exact path with case preserved.
  3. Check whether the build copied the asset, renamed it with a hash, or excluded it.
  4. Confirm the response is a real 404, not an HTML fallback returned with 200.
FindingCorrect repairMisleading repair
File absent from final buildFix the build/copy rule and rebuildAdd a random production redirect
Filename case differsMake source and reference case-consistentRely on a case-insensitive laptop filesystem
Hashed filename changedLet the build rewrite the HTML referenceHard-code the previous hash
URL returns app fallback HTMLCorrect static route/fallback behaviorTreat 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 contractRuntime repair
Self-contained single HTML fileBundle or inline the runtime, styles, and required data
Supported multi-file static bundlePackage relative local scripts with the entry point
General static website projectKeep build-generated chunks and configure routes/headers correctly
Application requiring secrets or a backendDeploy 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:

  1. move first-party code into packaged same-origin files when the host supports multi-file decks;
  2. use a build that emits CSP-compatible hashes or nonces when the serving layer can supply them;
  3. for a deliberately self-contained artifact, make the code and host policy agree before upload;
  4. 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.

GateWhat to checkEvidence to retain
ArtifactByte count/hash changed only as intended; file tree completeBuild ID, hash, tree or manifest
TransportEntry point, affected assets, scripts, fonts, and media return expected status/typeNetwork export or named request/status list
BrowserNo new first-party console failures; affected control now worksBrowser/version, steps, observed state
PresentationFirst/last slide, keyboard, touch, links, charts, fullscreen, mobileExplicit pass/fail checklist
Reader policyCorrect visibility, expiry, password/allowlist, download/embed behaviorClean-session observation, not owner session
LifecycleUpdate preserved the intended URL/version; rollback path knownDeck/deployment ID and current version
MeasurementTest traffic is labeled or excluded; real recipient evidence remains separateAnalytics 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.