vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.69k stars 26.62k forks source link

[NEXT-706] Turborepo with NextJS 13, error when import server component from package #44653

Open matteo2437 opened 1 year ago

matteo2437 commented 1 year ago

Verify canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.4.0: Mon Feb 21 20:35:58 PST 2022; root:xnu-8020.101.4~2/RELEASE_ARM64_T6000
Binaries:
  Node: 18.12.1
  npm: 9.2.0
  Yarn: N/A
  pnpm: 7.18.0
Relevant packages:
  next: 13.0.6
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/matteo2437/turborepo-test

To Reproduce

With this next.config.js settings: module.exports = { reactStrictMode: true, experimental: { appDir: true, transpilePackages: ["ui"], }, };

Run the app called docs and you will see this error message: Error: Unsupported Server Component type: undefined

Describe the Bug

The problem is that I can't use any server component exported from a Turborepo package and imported in a Turborepo Next js 13 app with appDir settings to true.

The problem is caused from the server component called Button inside the UI package that is imported in the docs app.

If you remove appDir from experimental the problem is fixed. So I'm not finding a way for using server component exported from a Turborepo package in an app that use appDir settings to true.

This is the error: image

And this is the server component exported from Ui package:

"use client"

import { useState } from "react";

export const Button = () => {
  const [count, setCount] = useState(0)

  return (
    <button
      onClick={() => {
        console.log(count)
        setCount(count => count + 1)
      }}
    >
      {count}
    </button>
  );
};

Expected Behavior

Import server component from Turborepo package in a Turborepo Next js 13 app without any error.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-706

DavidTParks commented 1 year ago

Did you find a resolution?

matteo2437 commented 1 year ago

Unfortunately no

AM-thedev commented 1 year ago

Try putting "use client" at the beginning of the top-most index of the ui package, i.e.

-components
 |
  -atoms
   |
    -Button
      |
      -Button.tsx
      -index.ts
    -index.ts  <- put "use client" at the top here
  -molecules
   |
    -etc.

Then import as something like import { Button } from "../components/atoms"

fran-cadenas-fu commented 1 year ago

Hello, did someone manage to find a solution?

Thanks,

glenarama commented 1 year ago

Changing to default export vs named fixes this for me - credit: https://stackoverflow.com/questions/75261466/unsupported-server-component-type-undefined-next-js-13

amritk commented 1 year ago

Yea it would be nice if there's a way to export named components and have this work. for nicer looking imports

JoseGarciaM commented 1 year ago

I have solved it. So basically you need to add tsup into your package where you are going to have your shareable components.

And add the following file in the root of your package folder "tsup.config.ts":

import { defineConfig, Options } from "tsup";

export default defineConfig((options: Options) => ({
  treeshake: true,
  splitting: true,
  entry: ["src/**/*.tsx"],
  format: ["esm"],
  dts: true,
  minify: true,
  clean: true,
  external: ["react"],
  ...options,
}));
}

Then add this file also at the root of your package folder "postbuild.js":

const fs = require('fs');
const path = require('path');

const indexPath = path.resolve(__dirname, 'dist', 'index.mjs');

fs.readFile(indexPath, 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading index.mjs file:', err);
    process.exit(1);
  }

  const result = `"use client";\n` + data;

  fs.writeFile(indexPath, result, 'utf8', (err) => {
    if (err) {
      console.error('Error writing index.mjs file:', err);
      process.exit(1);
    }
    console.log('index.mjs file updated successfully.');
  });
});

Then in your package.json add these scripts:

"scripts": {
    "build": "tsup && node postbuild.js",
    "dev": "tsup && node postbuild.js --watch",
    "check-types": "tsc --noEmit"
  },
jasonkylefrank commented 1 year ago

Changing to default export vs named fixes this for me - credit: https://stackoverflow.com/questions/75261466/unsupported-server-component-type-undefined-next-js-13

Are there some downsides to this approach that we should be aware of?

That approach solved the error for me in my Turborepo. But I'd like to hear if there are other consequences that I should be thinking about.

As an example of the adjustments I made to solve the error, I changed my packages/ui/src/Anchor.tsx component to use a default export. Then I updated my packages/ui/src/index.tsx file to export that default like this:

// export * from "./Anchor";  <-- OLD
export { default as Anchor } from "./Anchor";  // <-- NEW

Because this index file still exports a named Anchor component, I did not have to update any of my import statements for the instances in my apps. So for example, in my apps/web/src/app/about/page.tsx, my import statement still remained as:

import { Anchor } from "ui";

So far so good, but I'm not yet sure if there are other unintended consequences to this approach.

rahulsingh3526 commented 10 months ago

I fixed the error . there were two things. in the app directory of turnbo repo you need to make sure the export const is not a arrow function . it should be a normal function .

`export function Signup(): JSX.Element { return ( <div style={{ height: "100vh", justifyContent: "center", display: "flex" }}> <div style={{ width: 400, border: " 1px solid black" }}>

    <input placeholder="password" type="password" />
    <button type="submit">Submit</button>
  </div>
</div>

); }`

this is my component even though it's name is Signup but the file name should be signup.( not with capital letters) . this ins now in my index folder of ui export { Signup } from "./signup";

also default keyword is not used at all . anywhere.

then use in your apps folder with import

import { Signup } from "ui";