This post explains a simple client-side pattern for loading scripts and handling failures, using content security measures and clear user messaging. It’s based on a page that combines a strict Content-Security-Policy, font and stylesheet preloads, a noscript fallback, and a small JavaScript loader that reports load errors and reveals a user-friendly alert.
What the page does
– Enforces a Content-Security-Policy (CSP) that limits resource sources: default to self, images and media allowed from self and data, objects blocked, and only specific inline styles and scripts allowed via sha256 hashes. This reduces attack surface by preventing unexpected external resources or inline code from executing.
– Preloads a font and links a stylesheet to improve rendering performance and avoid flash of unstyled content.
– Includes a noscript fallback that informs users JavaScript is disabled and asks them to enable it.
– Keeps a hidden visible alert element (#loading-error) to show an explanatory message if required client files fail to load.
– Implements a loadScript function that returns a Promise, dynamically injects script tags, and sets onload and onerror handlers. On error it logs details, displays the loading error message, and rejects the promise with a helpful error.
– Uses loadScript to load a local errors.js first. If that succeeds it appends a second script pointing to a public script endpoint with a reload query string. The second script gets its own error handler that logs and calls a handleScriptError function.
Why this pattern is useful
– Progressive delivery: load a minimal, trusted script first (errors and diagnostics), then load larger or remote scripts only if diagnostics are available.
– Better user feedback: instead of a silent failure or broken UI, the page shows an accessible error message explaining probable causes (network, extensions, browser settings) and suggests actions like checking connection or disabling ad blockers.
– Security posture: CSP plus explicit hashes prevents execution of unexpected inline code and limits where resources can be fetched from.
– Centralized error handling: the loader centralizes load success/failure so you can log, show UI, and optionally retry or fall back.
Key implementation notes
– loadScript creates a script element, attaches onload and onerror, appends to document.body, and returns a Promise that resolves or rejects accordingly.
– On script load failure the code reveals #loading-error (display block) and prints error information to the console, providing a clear place to hook diagnostics or reporting.
– The second-level script has its own onerror behavior and calls handleScriptError for application-specific fallback behavior.
Best practices and improvements
1) Provide graceful fallbacks: keep the UI usable without the optional script where possible. If the main app script fails, consider rendering a static fallback or reduced functionality UI.
2) Add retry and backoff: for transient network failures, implement a limited retry strategy with exponential backoff before showing the final error message.
3) Use Subresource Integrity (SRI) when loading third-party scripts: add integrity attributes to verify content hasn’t been tampered with.
4) Consider async/defer: when appropriate, use async or defer attributes to avoid blocking parsing if scripts are noncritical.
5) Improve diagnostics reporting: send error metadata (user agent, network information, CSP violations) to your monitoring endpoint from errors.js so you can triage failures.
6) Avoid brittle inline hashes: CSP style and script hashes change when you edit inline content. Prefer external stylesheets and scripts coupled with nonces or strict host allowlists when possible.
7) Use crossorigin for fonts and cross-origin scripts when necessary, and configure server CORS headers to match.
8) Provide a clearer user action path: the alert should offer next steps (reload, contact support link, or try a different browser) and be accessible to screen readers (role=alert and aria-live are good starts).
9) Consider Service Worker caching: for repeat visits, a Service Worker can cache core assets and serve them offline or when the network is flaky.
10) Log granularly: distinguish between DNS failures, blocked resources, network timeouts, and CSP rejections. This helps find whether the issue is an ad blocker, CSP misconfiguration, or an origin outage.
Quick debugging checklist for users and admins
For users:
– Reload the page and try using a different browser.
– Disable browser extensions (especially ad blockers and privacy extensions) and reload.
– Check your network connection and try again.
For developers and ops:
– Reproduce in different networks to rule out caching/proxy/CDN issues.
– Inspect the browser console for CSP violation reports and script load errors.
– Verify the files exist at the expected paths and are reachable (status 200) and that CDNs are up.
– Check server response headers for correct Content-Type, CORS, and caching headers.
– If CSP blocks a resource, update the policy safely (prefer host allowlists and SRI over loosening inline allowances).
Conclusion
This lightweight pattern—CSP, preload, noscript fallback, and a promise-based script loader with explicit error UI—balances security, usability, and observability. It gives end users clear guidance when things go wrong, allows developers to capture and diagnose failures, and keeps the page safer from unexpected third-party behavior. Implement the suggested improvements (retry, SRI, reporting, and graceful fallbacks) to make the experience more robust and easier to support.