Skip to content

Dev-only first-visit failure with dynamic client entry in React Router “framework mode” (Vite 7) #14228

@garronej

Description

@garronej

Repro

npx degit https://github.com/keycloakify/oidc-spa/examples/react-router-framework oidc-spa-react-router
cd oidc-spa-react-router
cp .env.local.sample .env.local
npm install
npm run dev
# open the printed dev URL and navigate once
  • This is React Router framework mode configured as SPA (ssr: false) with only oidc-spa set up.
  • I also recorded a short video showing the behavior (first-visit failure + one-time redirect):

(There is sound)

Screen.Recording.2025-08-22.at.18.27.15.mov

System Info

System:
    OS: macOS 15.3
    CPU: (12) arm64 Apple M2 Max
    Memory: 12.61 GB / 64.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 22.12.0 - ~/.nvm/versions/node/v22.12.0/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v22.12.0/bin/yarn
    npm: 11.4.1 - ~/.nvm/versions/node/v22.12.0/bin/npm
    pnpm: 10.13.1 - ~/.nvm/versions/node/v22.12.0/bin/pnpm
    bun: 1.2.2 - /opt/homebrew/bin/bun
  Browsers:
    Chrome: 139.0.7258.128
    Chrome Canary: 141.0.7369.0
    Edge: 139.0.3405.102
    Safari: 18.3
  npmPackages:
    @react-router/dev: 7.8.1 => 7.8.1 
    @react-router/fs-routes: 7.8.1 => 7.8.1 
    @react-router/node: 7.8.1 => 7.8.1 
    @react-router/serve: 7.8.1 => 7.8.1 
    react-router: 7.8.1 => 7.8.1 
    vite: 7.1.3 => 7.1.3

Used Package Manager

npm

TL;DR
On a fresh npm run dev start, the very first navigation to the app fails with:

Failed to fetch dynamically imported module http://localhost:7173/app/entry.client.tsx

A simple refresh fixes it. Subsequent navigations are fine. Production builds are fine. Also, on the very first attempt to visit a protected route, React Router appears to “hijack” and redirect to / once; this never happens again.

This is confusing for newcomers because their first interaction is an error + odd redirect, even though everything works after a refresh.


Context

I’m building an OIDC client for Single Page Applications called oidc-spa.
I’m currently working on the official React Router framework mode integration example. This behavior does not just affect me, it will potentially impact any team integrating React Router with OIDC client logic in a similar way.


What happens

  1. Start dev server (npm run dev).
  2. Open the app for the first time.
  3. The browser console shows:
    Failed to fetch dynamically imported module http://localhost:7173/app/entry.client.tsx
    
    The app doesn’t hydrate/mount.
  4. Refresh the page → everything works.
  5. Quit and restart the dev server → subsequent first visits work (only the very first fresh start causes it).
  6. First attempt to navigate to a protected route causes a one-time redirect to /. Further attempts behave normally.

What I expect

  • No failure on the first visit after a fresh dev server start.
  • No one-time “mystery” redirect on the first attempt to visit a protected route.
  • Same behavior as subsequent navigations / production build.

Why my setup is a bit unusual (but intentional)

I need to run some OIDC bootstrap logic before any other JS runs and sometimes cancel app initialization early (e.g., when the OIDC authorization response lands). So the client entry conditionally lazy-loads the real app:

app/entry.client.tsx

import { oidcEarlyInit } from "oidc-spa/entrypoint";

const { shouldLoadApp } = oidcEarlyInit({
  freezeFetch: true,
  freezeXMLHttpRequest: true
});

if (shouldLoadApp) {
  import("./entry.client.lazy");
}

app/entry.client.lazy.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
import { getOidc, OidcProvider } from "./oidc.client";

getOidc().finally(() =>
  ReactDOM.hydrateRoot(
    document,
    <React.StrictMode>
      {/*
       * NOTE: Do not provide a fallback prop to the <OidcProvider>
       * Use the HydrateFallback of ./root.tsx instead
       */}
      <OidcProvider>
        <HydratedRouter />
      </OidcProvider>
    </React.StrictMode>
  )
);

I realize this isn’t the canonical entry pattern, but it’s a common need for SPA OIDC clients to gate app boot.


Observations

  • Happens only on the very first page load after the very first dev-server start in a fresh terminal.
  • After a single manual refresh, everything settles.
  • Production build (react-router build && react-router serve) works perfectly.

Request

  • Can you confirm whether conditional dynamic import at the client entry is supported in framework mode?
  • If yes, could this be a dev server preload/resolution bug around the very first request?
  • If not supported, what’s the recommended way to gate app boot (i.e., run code before anything else and optionally cancel boot) without tripping the dev server?

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions