clerk/javascript

@clerk/remix does not work with the root layout export

AdiRishi opened this issue · 10 comments

Preliminary Checks

Reproduction / Replay Link

https://github.com/AdiRishi/repro-remix-cf-with-layout

Publishable key

pk_live_Y2xlcmsubmFhbWRlby5vcmck

Description

Remix has recently released a way to have a stable app shell, preventing various issues. See https://remix.run/docs/en/main/file-conventions/root#layout-export

However, using this stable layout function seems to break the Clerk Remix integration.

Steps to reproduce:

The reproduction repository I've made uses a Remix Cloudflare setup. There is a commit in that repository (commit 104af3f17bf6c375e8b970c4d8f77a51ffb43698) that has a working version just before the layout function change if you want to see a working version of the app.
I've made the reproduction repository by effectively following the quickstart guide.

  1. Go to the reproduction repository and run pnpm install
  2. Update the .dev.vars file with your own CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY
  3. Run pnpm dev and go to http://localhost:8788

Expected behavior:

This should behave the exact same way as if we were not using the layout export. Aka things should work.

Actual behavior:

In other projects I've seen this behave differently, but in the reproduction repository I've made we can see an infinite 401 loop.

Environment

System:
  OS: macOS 14.3.1
  CPU: (8) arm64 Apple M1 Pro
  Memory: 1.06 GB / 32.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 20.11.0 - ~/.nvm/versions/node/v20.11.0/bin/node
  Yarn: 1.22.19 - ~/.nvm/versions/node/v20.11.0/bin/yarn
  npm: 10.5.0 - ~/.nvm/versions/node/v20.11.0/bin/npm
  pnpm: 8.15.4 - ~/.nvm/versions/node/v20.11.0/bin/pnpm
  bun: 1.0.25 - /opt/homebrew/bin/bun
Browsers:
  Chrome: 122.0.6261.69
  Safari: 17.3.1
npmPackages:
  @clerk/remix: ^3.1.19 => 3.1.19
  @cloudflare/workers-types: ^4.20240222.0 => 4.20240222.0
  @remix-run/cloudflare: ^2.8.0 => 2.8.0
  @remix-run/cloudflare-pages: ^2.8.0 => 2.8.0
  @remix-run/css-bundle: ^2.8.0 => 2.8.0
  @remix-run/dev: ^2.8.0 => 2.8.0
  @remix-run/react: ^2.8.0 => 2.8.0
  @types/react: ^18.2.60 => 18.2.60
  @types/react-dom: ^18.2.19 => 18.2.19
  @typescript-eslint/eslint-plugin: ^7.1.0 => 7.1.0
  eslint: ^8.57.0 => 8.57.0
  eslint-import-resolver-typescript: ^3.6.1 => 3.6.1
  eslint-plugin-import: ^2.29.1 => 2.29.1
  eslint-plugin-jsx-a11y: ^6.8.0 => 6.8.0
  eslint-plugin-react: ^7.33.2 => 7.33.2
  eslint-plugin-react-hooks: ^4.6.0 => 4.6.0
  isbot: ^5.1.1 => 5.1.1
  react: ^18.2.0 => 18.2.0
  react-dom: ^18.2.0 => 18.2.0
  typescript: ^5.3.3 => 5.3.3
  wrangler: ^3.30.0 => 3.30.0

I confirm the issue.

The Clerk integration appears to be totally broken in the latest version of Remix (currently v2.8.0).
I'm facing the exact same issue with the infinite 401 loop.
The official docs (even the beta ones) don't mention a word about it.

After looking around a bit I found the following issue which might be somehow related:
#965

Looks like it was addressed by #1444 but unfortunately in reality the issue wasn't fixed.

Any help would be much appreciated 🙏

Hey @kstratis , just as an FYI you can use Clerk with remix 2.8.0 (I'm doing it right now), you just have to avoid the root layout export, and use the classic way of exporting the entire app shell from App, ErrorBoundary and HydrateFallback

@AdiRishi When you say use the classic way can you give example. Were using Remix/Vite an no matter what I do it won't work

@AdiRishi Yes, looks like you are correct. By removing the layout export everything works as expected.
@AlexanderKaran Here's what's working in latest Remix (v2.8.0) using vite with express js:

root.tsx

import type { MetaFunction, LoaderFunction } from "@remix-run/node";
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "@remix-run/react";
import { ClerkApp, ClerkErrorBoundary } from "@clerk/remix";

import { rootAuthLoader } from "@clerk/remix/ssr.server";

export const loader: LoaderFunction = args => {
  return rootAuthLoader(args, ({ request }) => {
    const { sessionId, userId, getToken } = request.auth;
    console.log(sessionId);
    console.log(userId);

    // fetch data
    return { yourData: 'here' };
  });
};

export const meta: MetaFunction = () => ([{
  charset: "utf-8",
  title: "New Remix App",
  viewport: "width=device-width,initial-scale=1",
}]);

export const ErrorBoundary = ClerkErrorBoundary();

function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default ClerkApp(App);

Regardless though, this needs to be fixed cause exporting layout is now the default.

Hello everyone and thanks for the report :) I just wanted to let you know that we're going to investigate this one this week so please expect an update from me or @desiprisg within the next few days.

Hello everyone and thanks for reporting that!
This issue is already fixed in our latest release, Clerk Core 2, which is currently in beta and is expected to be shipped as a GA release within the next two weeks.
Unfortunately, fixing this in v4 requires a breaking change - even though we suggest giving the beta version a try, here's a workaround you can use in the meantime:

import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction, LoaderFunction } from "@remix-run/node";
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  isRouteErrorResponse,
  useLoaderData,
  useRouteError,
  useRouteLoaderData,
} from "@remix-run/react";
import { rootAuthLoader } from "@clerk/remix/ssr.server";
import { ClerkProvider } from "@clerk/remix";
import { ClerkApp } from "@clerk/remix";
import { ClerkErrorBoundary } from "@clerk/remix";

export const links: LinksFunction = () => [
  ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
];

export const loader: LoaderFunction = (args) => rootAuthLoader(args);

export function Layout({ children }: { children: React.ReactNode }) {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    const { __clerk_ssr_interstitial_html } =
      error?.data?.clerkState?.__internal_clerk_state || {};
    if (__clerk_ssr_interstitial_html) return ClerkErrorBoundary();
  }

  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

function App() {
  return <Outlet />;
}

export default ClerkApp(App);

If you have any questions please reply here and I will take a look!

So we can upgrade to the beta and it will work without the workaround?

Thank you for your input @octoper!

I've had the chance to test your solution, and it indeed resolves the issue.

Looks like the root of the confusion lies in the documentation: The beta documentation mirrors the content found in the latest stable version almost exactly; Your recommendation is absolutely essential for the current version to function correctly. However, it appears the Clerk core 2 installation guidelines, which should be present in the beta documentation, are missing. By chance, I discovered the necessary instructions here. Integrating these steps with the original examples from the latest documentation —with minor adjustments for layout— leads to a seamless setup as expected. Here's my working root.tsx using Clerk core 2:

import type { LoaderFunction } from "@remix-run/node";

import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "@remix-run/react";
import { ClerkApp } from "@clerk/remix";
import { rootAuthLoader } from "@clerk/remix/ssr.server";

export const loader: LoaderFunction = (args) => rootAuthLoader(args);

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

function App() {
  return <Outlet />;
}

export default ClerkApp(App);

I hope this clarification helps, and I suggest you consider updating the beta documentation to include the Clerk core 2 installation instructions explicitly.


P.S

So we can upgrade to the beta and it will work without the workaround?

@AlexanderKaran That is correct. Use my example above.

I can confirm updating to the beta version fixes the issue.

btw your upgrader is so clutch congratulations, what an incredible piece of software!!