#RR7 app has no working JavaScript when deployed to Vercel

1 messages · Page 1 of 1 (latest)

wary plover
#

I migrated a Remix 2 app to RR7 and now when I deploy to Vercel, the app seems like it's hanging, or as if the build process didn't generate all the files. I am able to navigate around the app but I can't do anything that requires JavaScript (clicking buttons, using fullstack components etc).

For further context, this app used to use remix-island. I tried using [email protected] and [email protected], the results are the same.

My entry.client:

import { HydratedRouter } from "react-router/dom";
import Backend from "i18next-http-backend";
import { createInstance } from "i18next";
import ICU from "i18next-icu";
import LanguageDetector from "i18next-browser-languagedetector";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { initReactI18next, I18nextProvider } from "react-i18next";
import { getInitialNamespaces } from "remix-i18next/client";

import i18n from "./i18n";

async function hydrate() {
  const instance = createInstance()
    .use(initReactI18next)
    .use(ICU)
    .use(Backend)
    .use(LanguageDetector);

  await instance.init({
    ...i18n,
    ns: getInitialNamespaces(),
    detection: {
      order: ["cookie", "htmlTag"],
      caches: [],
    },
  });

  startTransition(() => {
    hydrateRoot(
      document!,
      <I18nextProvider i18n={instance}>
        <StrictMode>
          <HydratedRouter />
        </StrictMode>
      </I18nextProvider>
    );
  });
}

if (window.requestIdleCallback) {
  window.requestIdleCallback(hydrate);
} else {
  // Safari doesn't support requestIdleCallback
  // https://caniuse.com/requestidlecallback
  window.setTimeout(hydrate, 1);
}
#

My entry.server:

import { PassThrough } from "node:stream";

import type {
  ActionFunctionArgs,
  AppLoadContext,
  LoaderFunctionArgs,
  EntryContext as ServerEntryContext,
} from "react-router";
import { createReadableStreamFromReadable } from "@react-router/node";
import { ServerRouter } from "react-router";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";
import { createInstance } from "i18next";
import { I18nextProvider, initReactI18next } from "react-i18next";
import Backend from "i18next-fs-backend";
import ICU from "i18next-icu";
import { getClientIPAddress } from "remix-utils/get-client-ip-address";

import i18next from "./services/i18next.server";
import i18n from "./i18n";
import { getEnv } from "./env.server";

// Reject/cancel all pending promises after 5 seconds
export const streamTimeout = 5000;

global.ENV = getEnv();

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  reactRouterContext: ServerEntryContext,
  loadContext: AppLoadContext
) {
  const callbackName = isbot(request.headers.get("user-agent"))
    ? "onAllReady"
    : "onShellReady";

  const ip = getClientIPAddress(request);

  if (ip) {
    responseHeaders.set("X-Custom-Forwarded-For", ip);
  }

  const instance = createInstance().use(initReactI18next).use(ICU).use(Backend);
  const lng = await i18next.getLocale(request);
  const ns = i18next.getRouteNamespaces(reactRouterContext);

  await instance.init({
    ...i18n,
    lng,
    ns,
    detection: {
      order: ["cookie", "htmlTag"],
      caches: [],
    },
  });

  return new Promise((resolve, reject) => {
    let didError = false;
    const { abort, pipe } = renderToPipeableStream(
      <I18nextProvider i18n={instance}>
          <ServerRouter context={reactRouterContext} url={request.url} />
      </I18nextProvider>,
      {
        [callbackName]() {
          responseHeaders.set("Content-Type", "text/html; charset=UTF-8");
          const body = new PassThrough();
          pipe(body);
          resolve(
            new Response(createReadableStreamFromReadable(body), {
              status: didError ? 500 : responseStatusCode,
              headers: responseHeaders,
            })
          );
        },
        onError(error: unknown) {
          didError = true;
          console.error(error);
        },
        onShellError(error: unknown) {
          reject(error);
        },
      }
    );
    // Automatically timeout the React renderer after 6 seconds, which ensures
    // React has enough time to flush down the rejected boundary contents
    setTimeout(abort, streamTimeout + 1000);
  });
}

export function handleError(
  error: unknown,
  { request }: LoaderFunctionArgs | ActionFunctionArgs
) {
  // Skip capturing if the request is aborted as Remix docs suggest
  // Ref: https://remix.run/docs/en/main/file-conventions/entry.server#handleerror
  if (request.signal.aborted) {
    return;
  }
  if (error instanceof Error) {
    console.error(error.stack);
  } else {
    console.error(error);
  }
}
buoyant shuttle
#

do you have any react-router-dom dependencies in your package.lock.json left?

wary plover
#
npm ls react-router-dom
`-- (empty)

It doesn't seem so

buoyant shuttle
#

mhh okay thats good at least 😄

#

and your build output log or deployment log in vercel got nothing in it right?

wary plover
#

yeah, no errors at all... I'm downgrading and upgrading dependencies to see if I something sticks

buoyant shuttle
#

and could you run it local with vercel dev?

wary plover
#

I didn't consider it, let me try

wary plover
#

with vercel dev it works too

buoyant shuttle
#

and still no errors anywhere? then its interesting whats different from vercel dev vs vercel prod

wary plover
#

no errors still. I tried adding some console logs and it seems they all get logged to vercel logs (console.logs inside loaders and components) but the only console log that gets logged to the browser is the one inside the hydrate function in entry.client

orchid root
#

Yea vercel doesn't work for me either

wary plover
#

I opened a support ticket for it, hopefully they can fix it soon

native spear
#

is there any open link to track it?

wary plover
#

No, sorry, I opened a support ticket in my vercel's organization. This was their response:

Hi Gustavo,

I have an update about this!

The team will work on supporting React Router 7; however, we don't have an estimate of when it will be done. They mentioned that even though the React Router docs are pitching this migration like it's a non-breaking change for apps, that's not really the case.

They recommended changing the framework preset from Remix to Vite (you might need to reconfigure the application after this). They also recommended reviewing this working template as an example: https://github.com/remix-run/react-router-templates/tree/main/vercel.

For us to investigate further, could you provide a minimal reproduction of the issue? Please provide a link to a public GitHub repository or CodeSandbox. Thank you!

Best,
Anna
▲ Senior Customer Success Engineer at Vercel

But I solved it differently as I couldn't wait for the response. After doing the RR7 migration, I deleted react-router.config.ts, changed my vite.config.ts to:

import { vitePlugin as remix } from "@remix-run/dev";
import { vercelPreset } from "@vercel/remix/vite";
import { defineConfig } from "vite";
import commonjs from "vite-plugin-commonjs";
import tsconfigPaths from "vite-tsconfig-paths";

const isVercel = process.env.VERCEL === "1";

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    remix({
      ...(isVercel && { presets: [vercelPreset()] }),
      future: {
        v3_fetcherPersist: true,
        v3_lazyRouteDiscovery: true,
        v3_relativeSplatPath: true,
        v3_routeConfig: true,
        v3_singleFetch: true,
        v3_throwAbortReason: true,
        unstable_optimizeDeps: true,
      },
    }),
    tsconfigPaths(),
    commonjs(),
  ],
  optimizeDeps: {
    include: [
      // ...
    ],
  },
  resolve: {
    alias: {
      // ...
    },
  },
  ssr: {
    noExternal: [
      // ...
    ],
  },
});

And kept my app/routes.ts file. With this I'm using react-router for all the components but the vite plugin for deploy

native kayak
#

I have converted my Remix application to RR7 and it's working just fine, I used this as my base template for the migration: https://github.com/remix-run/react-router-templates/tree/main/vercel

NOTE: I also updated pnpm -> bun, and this causes a lot of issues with react-router-dom, I had to remove all packages which did not install RR7, for example:

  • @vercel/remix
  • remix-auth -> updated to v4
  • remix-utils
  • any @remix-run/* package.