Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client components used only inside server actions are not added to the module graph #58125

Open
1 task done
Dwlad90 opened this issue Nov 7, 2023 · 2 comments
Open
1 task done
Labels
bug Issue was opened via the bug report template.

Comments

@Dwlad90
Copy link

Dwlad90 commented Nov 7, 2023

Link to the code that reproduces this issue

https://github.com/Dwlad90/elegant-cloud-zckzqc/tree/draft/goofy-mountain

To Reproduce

  1. Install dependencies with npm i
  2. Run the app in development mode (next dev)
  3. Go to http://localhost:3000
  4. See console error saying " Error: Could not find the module "/Users/vladislavbuinovski/Projects/elegant-cloud-zckzqc/components/SA_CLNT_Component/Component.tsx#" in the React Client Manifest. This is probably a bug in the React Server Components bundler."
  5. As a result, some styles are missing. in Server: Test server (Test server should be pink with a yellow background)
  6. Side-effect import of the client component or server action (as seen, commented out, on lines 5 and 6 in ServerStyles.tsx force these modules into the graph, and causes the styles in 5 to be displayed

Current vs. Expected behavior

Client side modules imported only in a server action module are not included in the React Client Manifest. They should be and the missing style should show up

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.1.0: Mon Oct  9 21:27:24 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T6000
Binaries:
  Node: 18.18.2
  npm: 10.1.0
  Yarn: 1.22.19
  pnpm: 8.9.2
Relevant Packages:
  next: 14.0.2-canary.18
  eslint-config-next: 14.0.2-canary.18
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

App Router

Additional context

No response

@Dwlad90 Dwlad90 added the bug Issue was opened via the bug report template. label Nov 7, 2023
@roggc
Copy link

roggc commented Feb 20, 2024

I am facing the same situation in an Action client component I defined, which what it does is wrap a server action in a Suspense component. This server action returns a client component. And then the error appears. The workaround is to include an import to the server action in a server component, RootLayout for example.

This is my Action client component:

"use client";

import { Suspense, useMemo } from "react";
import { usePropsChangedKey } from "@/app/hooks";
import useSWR from "swr";

const fetcher = (action, props) =>
  new Promise((r) => setTimeout(async () => r(await action(props))));

const fetcherSWR = ([, action, props]) => fetcher(action, props);

const Error = ({ errorMessage }) => <>Something went wrong: {errorMessage}</>;

const getReader = () => {
  let done = false;
  let promise = null;
  let value;
  return {
    read: (fetcher) => {
      if (done) {
        return value;
      }
      if (promise) {
        throw promise;
      }
      promise = new Promise(async (resolve) => {
        try {
          value = await fetcher();
        } catch (error) {
          value = <Error errorMessage={error.message} />;
        } finally {
          done = true;
          promise = null;
          resolve();
        }
      });

      throw promise;
    },
  };
};

const Read = ({ fetcher, reader }) => {
  return reader.read(fetcher);
};

const ReadSWR = ({ swrArgs, fetcher }) => {
  return useSWR(swrArgs, fetcher, {
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    suspense: true,
  }).data;
};

export default function Action({
  action,
  children = <>loading...</>,
  isSWR = false,
  ...props
}) {
  const propsChangedKey = usePropsChangedKey(...Object.values(props));

  const reader = useMemo(() => getReader(), [propsChangedKey]);

  const propsForSWR = useMemo(() => props, [propsChangedKey]);
  const swrArgs = useMemo(
    () => [propsChangedKey, action, propsForSWR],
    [action, propsForSWR, propsChangedKey]
  );

  return (
    <Suspense fallback={children}>
      {isSWR ? (
        <ReadSWR swrArgs={swrArgs} fetcher={fetcherSWR} />
      ) : (
        <Read fetcher={() => fetcher(action, props)} reader={reader} />
      )}
    </Suspense>
  );
}

This is the hook (for those who are curious about it):

import { useState, useEffect, useRef } from "react";

export function usePropsChangedKey(...args) {
  const [propsChangedKey, setPropsChangedKey] = useState(0);
  const isFirstRenderRef = useRef(false);

  useEffect(() => {
    isFirstRenderRef.current = true;
  }, []);

  useEffect(() => {
    if (!isFirstRenderRef.current) {
      setPropsChangedKey((k) => k + 1);
    } else {
      isFirstRenderRef.current = false;
    }
  }, [...args]);

  return propsChangedKey;
}

This is how you call the Action client component from a client or server component (client in this case):

"use client";

import Action from "@/app/action";
import { greeting } from "@/app/actions/greeting";
import { useState } from "react";

export default function Client1() {
  const [userId, setUserId] = useState(1);

  return (
    <>
      <Action action={greeting} userId={userId} />
      <button
        onClick={() => {
          setUserId(2);
        }}
      >
        click
      </button>
    </>
  );
}

And this is the greeting server action code:

"use server";

import Greeting from "@/app/action-components/greeting";
import MyError from "@/app/action-components/my-error";

const DELAY = 500;

const users = [
  { id: 1, username: "roggc" },
  { id: 2, username: "roger" },
];

export async function greeting({ userId }) {
  try {
    const username = await new Promise((r) => {
      setTimeout(() => {
        const user = users.find((u) => u.id === userId);
        if (user) {
          r(user.username);
        }
      }, DELAY);
    });

    // throw new Error("crash!");
    return <Greeting username={username} />;
  } catch (error) {
    return <MyError errorMessage={error.message} />;
  }
}

Now, without the workaround commented at the beginning, you get this error:

⨯ Error: Could not find the module "C:\Users\roggc\dev\nextjs\test1\app\action-components\greeting.js#" in the React Client Manifest. This is probably a bug in the React Server Components bundler. at stringify (<anonymous>)

The workaround, as I said, is to import the server action in a server component, for example the RootLayout:

import { Inter } from "next/font/google";
import { greeting } from "@/app/actions/greeting"; // <-- this is necessary, if not fails to compile

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

@JonathanVelkeneers
Copy link

I'm facing the same issue.
I am trying to use the AI-SDK-RSC to stream a client component in a server action.
This is the only place this client component is used so I'm also facing the "Could not find the module" error.
https://sdk.vercel.ai/docs/ai-sdk-rsc/streaming-user-interfaces

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template.
Projects
None yet
Development

No branches or pull requests

3 participants