# How OpenSEO Handles Automatic Chunk Reload Detection for Deployed Applications

> Learn how OpenSEO automatically detects and reloads stale chunks after deployments using Vite's vite:preloadError event for seamless application updates.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-09-02

---

**OpenSEO uses Vite's `vite:preloadError` event to catch failed dynamic imports and automatically reload the page when stale chunks are detected after a deployment.**

Automatic chunk reload detection ensures users with old browser tabs never encounter broken functionality when a new version of your app goes live. The [every-app/open-seo](https://github.com/every-app/open-seo) repository implements this pattern to solve the common problem of hash-mismatched JavaScript chunks after a Vite build.

## The Problem: Stale Chunks After Deployment

When Vite rebuilds an application, it generates **content-hashed filenames** for code-split chunks. A user who loaded your app before the deploy holds references to the old chunk hashes. If they navigate to a route that triggers a dynamic import, the browser requests a chunk that no longer exists on the server. Without intervention, this produces a silent failure or a blank screen.

## How OpenSEO Detects Chunk Failures

The detection mechanism in [[`src/router.tsx`](https://github.com/every-app/open-seo/blob/main/src/router.tsx)](https://github.com/every-app/open-seo/blob/main/src/router.tsx) leverages a Vite-specific browser event that fires exclusively in production builds.

### The `vite:preloadError` Event

Vite emits this custom event when a dynamic `import()` call fails due to a network error or module resolution failure. OpenSEO registers a global listener immediately upon client initialization—before React hydration completes—to intercept these errors as early as possible.

The implementation follows four steps:

1. **Register early** – Add the listener as soon as `window` is available
2. **Rate-limit reloads** – Check `sessionStorage` for a recent reload timestamp
3. **Record the attempt** – Store the current time if the cooldown has passed
4. **Execute the reload** – Prevent default handling and call `window.location.reload()`

## Implementation in [`src/router.tsx`](https://github.com/every-app/open-seo/blob/main/src/router.tsx)

The complete logic fits in a single event listener registration:

```tsx
// src/router.tsx – automatic chunk-reload detection
if (typeof window !== "undefined") {
  window.addEventListener("vite:preloadError", (event) => {
    try {
      const reloadedAt = Number(
        window.sessionStorage.getItem("vite-preload-error-reloaded-at") ?? 0,
      );
      // Allow at most one reload per 30 seconds
      if (Date.now() - reloadedAt < 30_000) return;
      window.sessionStorage.setItem(
        "vite-preload-error-reloaded-at",
        String(Date.now()),
      );
    } catch {
      // If sessionStorage is unavailable, skip the reload to avoid loops
      return;
    }
    event.preventDefault();
    window.location.reload();
  });
}

```

### Rate-Limiting Prevents Reload Loops

The **30-second cooldown** stored under the **`vite-preload-error-reloaded-at`** key protects against edge cases where:

- The server serves inconsistent builds
- A browser extension blocks the new chunk
- Network conditions cause repeated failures

Without this guard, a persistent error condition would trap users in an infinite reload cycle.

### Graceful Degradation

The `try/catch` block ensures the handler fails silently if `sessionStorage` is unavailable. This accommodates:

- Private browsing modes with restricted storage
- Security policies that disable `Storage` APIs
- Embedded contexts like WebViews with limited permissions

## Key Differences: Development vs. Production

| Environment | `vite:preloadError` Behavior |
|-------------|------------------------------|
| **Development** | Event does not fire; Vite serves files from memory without hashing |
| **Production** | Event fires on chunk load failures; enables automatic recovery |

This distinction means the automatic chunk reload detection only activates in built applications. Your development workflow remains unaffected.

## Where This Pattern Fits in Your Architecture

OpenSEO places this logic in **[`src/router.tsx`](https://github.com/every-app/open-seo/blob/main/src/router.tsx)** because:

- It loads early in the application lifecycle
- It executes before route-based code splitting begins
- It remains decoupled from individual component implementations

You can adopt the same pattern in any Vite-based React, Vue, or Svelte application by adding the listener in your entry point or router initialization module.

## Summary

- OpenSEO catches stale chunk errors using Vite's **`vite:preloadError`** event
- The handler in [`src/router.tsx`](https://github.com/every-app/open-seo/blob/main/src/router.tsx) implements **30-second rate limiting** via `sessionStorage`
- Reloads execute only when sufficient time has passed, preventing infinite loops
- The mechanism is **production-only** and gracefully handles storage restrictions
- Users automatically receive the latest deployment without manual intervention

## Frequently Asked Questions

### What triggers the `vite:preloadError` event?

Vite emits this event when a dynamic import fails to load, typically because the requested chunk filename no longer exists on the server after a new deployment. This occurs when chunk hashes change between builds and a client with cached HTML references the old files.

### Why does OpenSEO use `sessionStorage` instead of `localStorage`?

`sessionStorage` resets when the tab closes, ensuring a fresh reload opportunity on the next visit. `localStorage` would persist the timestamp across sessions, potentially blocking legitimate reloads hours after a transient error occurred.

### Can this pattern cause data loss for users?

A page reload does discard unsaved client-side state. However, the alternative—leaving users on a broken application with failed chunks—provides a worse experience. For critical user input, implement auto-save to `localStorage` or a backend before navigation triggers dynamic imports.

### Does this work with other bundlers like webpack or Rollup?

No. The `vite:preloadError` event is specific to Vite's runtime. webpack offers similar functionality through the `__webpack_chunk_load__` error hook, but the implementation differs. You would need to adapt the detection logic to your bundler's chunk loading API.