Closed fredrivett closed 8 months ago
Hey Fred
Without having quite gotten to the bottom of this, I thought I'd share my findings anyway as someone may be able to help pick this up and find a full solution.
I found there's a setting in the withTwin.js
file that's causing the error.
// withTwin.js
config.module.rules.push({
test: /\.(tsx|ts)$/,
include: includedDirs,
use: [
options.defaultLoaders.babel, // < Commenting this line out removes the error
// ...
],
});
In the defaultLoaders the hasServerComponents
option is causing the error:
{
loader: 'next-swc-loader',
options: {
hasServerComponents: true, // < This set as `true` causes the error,
// ...
}
}
Right now I'm unsure why hasServerComponents: true
causes the use client;
directive to be stripped, but I was able to patch the loader like this:
const path = require("path");
// The folders containing files importing twin.macro
const includedDirs = [path.resolve(__dirname, "src")];
module.exports = function withTwin(nextConfig) {
return {
...nextConfig,
webpack(config, options) {
const { dev, isServer } = options;
config.module = config.module || {};
config.module.rules = config.module.rules || [];
// Make the loader work with the new app directory
// https://github.com/ben-rogerson/twin.macro/issues/788
const patchedDefaultLoaders = options.defaultLoaders.babel;
patchedDefaultLoaders.options.hasServerComponents = false;
config.module.rules.push({
test: /\.(tsx|ts)$/,
include: includedDirs,
use: [
patchedDefaultLoaders,
{
loader: "babel-loader",
options: {
sourceMaps: dev,
plugins: [
require.resolve("babel-plugin-macros"),
[
require.resolve("babel-plugin-styled-components"),
{ ssr: true, displayName: true },
],
[
require.resolve("@babel/plugin-syntax-typescript"),
{ isTSX: true },
],
],
},
},
],
});
if (!isServer) {
config.resolve.fallback = {
...(config.resolve.fallback || {}),
fs: false,
module: false,
path: false,
os: false,
crypto: false,
};
}
if (typeof nextConfig.webpack === "function") {
return nextConfig.webpack(config, options);
}
return config;
},
};
};
I'm not sure of the implications of this but the repo you posted now builds and can be served.
This is ace, thanks so much for investigating this and your work in general here @ben-rogerson, it's much appreciated.
I can confirm that fix also works in my actual project where I first bumped into this issue.
It's an odd one, I've had a quick search myself and couldn't see anything else mentioning this or anything similar, so may be one that's best to be left open until the root explanation is found? Up to you.
If anything strange comes up due to this I'll report back.
Thanks again.
No worries, yeah keep us in the loop on this if you can.
I'm keen to keep this open as the app
directory feature looks to be where next is heading.
π€ the patch is a good fix - it may be possible to even remove options.defaultLoaders.babel
from the array altogether without issues.
I'm still getting this error:
./src/lib/registry.tsx
ReactServerComponentsError:
The "use client" directive must be placed before other expressions. Move it to the top of the file to resolve this issue.
,-[/<redacted>/src/lib/registry.tsx:1:1]
1 | import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "react/jsx-dev-runtime";
2 | var _s = $RefreshSig$();
3 | "use client";
: ^^^^^^^^^^^^^
4 | import React, { useState } from "react";
5 | import { useServerInsertedHTML } from "next/navigation";
6 | import { ServerStyleSheet, StyleSheetManager } from "styled-components";
`----
Import path:
./src/lib/registry.tsx
Any ideas? Thanks!
// Make the loader work with the new app directory
// https://github.com/ben-rogerson/twin.macro/issues/788
const patchedDefaultLoaders = options.defaultLoaders.babel;
patchedDefaultLoaders.options.hasServerComponents = false;
// TODO(igm): can't use react refresh
patchedDefaultLoaders.options.hasReactRefresh = false;
Patched using the above. Not sure what hasReactRefresh
does, but it seems hot reload still works.
So is there any solution yet?
```ts // Make the loader work with the new app directory // https://github.com/ben-rogerson/twin.macro/issues/788 const patchedDefaultLoaders = options.defaultLoaders.babel; patchedDefaultLoaders.options.hasServerComponents = false; // TODO(igm): can't use react refresh patchedDefaultLoaders.options.hasReactRefresh = false;
Patched using the above. Not sure what hasReactRefresh does, but it seems hot reload still works.
Have tried all these - server-side of the CSS now working.
However, whenever I use the tw
attribute, I'm getting the following error in the browser:
Warning: Prop `className` did not match. Server: "Navigation___StyledDiv-sc-f28daec0-0 VwAlI" Client: "Navigation___StyledDiv-sc-18d85156-0 jdQjVx"
at div
at O (webpack-internal:///(app-client)/./node_modules/styled-components/dist/styled-components.browser.esm.js:33:23409)
at header
at Navigation
at StyledComponentsRegistry (webpack-internal:///(app-client)/./src/lib/registry.tsx:18:11)
at body
at html
at RedirectErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/redirect-boundary.js:73:9)
at RedirectBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/redirect-boundary.js:81:11)
at ReactDevOverlay (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:70:9)
at NotFoundErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/not-found-boundary.js:51:9)
at NotFoundBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/not-found-boundary.js:59:11)
at HotReload (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:318:11)
at Router (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/app-router.js:150:11)
at ErrorBoundaryHandler (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/error-boundary.js:77:9)
at ErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/error-boundary.js:104:11)
at AppRouter (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/app-router.js:395:13)
at ServerRoot (webpack-internal:///(app-client)/./node_modules/next/dist/client/app-index.js:166:11)
at RSCComponent
at Root (webpack-internal:///(app-client)/./node_modules/next/dist/client/app-index.js:182:11)
window.console.error @ app-index.js:31
console.error @ hydration-error-info.js:45
overrideMethod @ console.js:213
printWarning @ react-dom.development.js:94
error @ react-dom.development.js:68
warnForPropDifference @ react-dom.development.js:31222
hydrateAttribute @ react-dom.development.js:32674
diffHydratedGenericElement @ react-dom.development.js:33074
diffHydratedProperties @ react-dom.development.js:33454
hydrateInstance @ react-dom.development.js:34448
prepareToHydrateHostInstance @ react-dom.development.js:6974
completeWork @ react-dom.development.js:18655
completeUnitOfWork @ react-dom.development.js:24746
performUnitOfWork @ react-dom.development.js:24551
workLoopConcurrent @ react-dom.development.js:24526
renderRootConcurrent @ react-dom.development.js:24482
performConcurrentWorkOnRoot @ react-dom.development.js:23339
workLoop @ scheduler.development.js:261
flushWork @ scheduler.development.js:230
performWorkUntilDeadline @ scheduler.development.js:537
Show 1 more frame
Show less
Seems like App Router is still giving me issues with all the functionality that first attracted me towards NextJS...
Will keep an eye on this thread. Panda CSS is getting some attention by fully supporting the appDir but I still prefer the DX from Twin. Keep up the great work!
I've updated the twin examples with the fix mentioned above and moved all of them to use the app directory - no issues so far.
@ben-rogerson I degit the next-emotion-typescript
sample but I'm having an error on the first run of npm run dev
.
On the other hand, next-styled-components-typescript
works. It seems this is a known limitation from emotion, whose appDir support isn't ready yet.
Looks like I'll have to roll that example back as I clearly didn't test it properly. I'm also having trouble avoiding the same errors and in the process of trying different workarounds.
Thanks for the response. I also faced a similar issue as faced by @rdgr as I degit the next-styled-components template yesterday. It worked fine all along, till I created a layout file for a route group. I had to add "use client" to the top of the page.tsx and layout.tsx files for the error to go away. But it doens't feel right. Am I supposed to "use client" on each page.tsx and layout.tsx that is has components styled with tw and css?
Thanks for the amazing work on this project though β€οΈ
@rdgr I've updated the next-emotion-typescript example to use the app dir + emotion latest. We need to use the jsx pragma at the moment - here are my findings.
@ben-rogerson Any ideas to work on nextjs 14?
Thank you for continuing to research the issue and updating the example project for next.js 14!
However, it seems that errors similar to the above continuously occur in the example project of next.js 14.
I tried to reproduce the error by creating a simple counter page project using useState on the example project(next-emotion-typescript).
If you are interested, please take a look and help those who are experiencing the same errorπ₯Ή
I believe I've fixed the error for Next.js 14. Furthermore, twin.macro
now works with server components!
Note that I am using ESM. My withTwin.mjs
:
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import babelPluginTypescript from "@babel/plugin-syntax-typescript";
import babelPluginMacros from "babel-plugin-macros";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import babelPluginTwin from "babel-plugin-twin";
import * as path from "path";
import * as url from "url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
// The folders containing files importing twin.macro
const includedDirs = [path.resolve(__dirname, "src")];
/** @returns {import('next').NextConfig} */
export default function withTwin(
/** @type {import('next').NextConfig} */
nextConfig,
) {
return {
...nextConfig,
compiler: {
...nextConfig.compiler,
styledComponents: true,
},
webpack(
/** @type {import('webpack').Configuration} */
config,
options,
) {
const { dev } = options;
config.module = config.module || {};
config.module.rules = config.module.rules || [];
config.module.rules.push({
test: /\.(tsx|ts)$/,
include: includedDirs,
use: [
{
loader: "babel-loader",
options: {
sourceMaps: dev,
plugins: [
babelPluginTwin,
babelPluginMacros,
// no more need for babel-plugin-styled-components
// see: https://nextjs.org/docs/architecture/nextjs-compiler#styled-components
[babelPluginTypescript, { isTSX: true }],
],
},
},
],
});
if (typeof nextConfig.webpack === "function") {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return nextConfig.webpack(config, options);
}
return config;
},
};
}
@macalinao
Thank you very much for your reply π
I can solve the problem I was experiencing by applying the withTwin.mjs
file above.
Here's some information for those who want to apply this file:
If you are using emotion
not styled components
, just delete styledComponents: true
.
Then you can work without installing styled components
.
There is no babel-plugin-twin
in the package.json of the example project(next-emotion-typescript) now, but installation is required to use the withTwin.mjs file above.
Thank you again for providing the answer to a problem I have been pondering for several days.
@macalinao Huge thanks for your work with the improved/fixed config π . I've verified it's working too and I've updated the following next examples with the improvements:
styled-components / styled-components (ts) / emotion / emotion (ts) / stitches (ts)
To add to the notes above:
babel-plugin-styled-components
for jsx syntax support..mjs
for the next.config and withTwin files only to avoid the require
imports. You're still able to use .js
versions of these files, just switch back to the require
imports.babel-plugin-twin
in next.config.mjs
but kept it commented out - this should make it easier to setup if needed.Closing this thread as I think we've finally found a good solution.
Sorry for re-openning this but none of the examples work with server components. Even your example uses "use client"
everywhere.
Sorry for re-openning this but none of the examples work with server components. Even your example uses
"use client"
everywhere.
Agreed. The example work in "client component" file (with "use client"). But when use tw`` in "server component", it still got the same error " createContext only works in Client Components. Add the "use client" directive at the top of the file to use it."
@ben-rogerson I think the next and t3app examples need to be updated also
@ciokan Same here... doesn't work with next js 14 app directory & server components... seems like it's styled-components problem(css in js); this is what next js doc says
Warning: CSS-in-JS libraries which require runtime JavaScript are not currently supported in Server Components. Using CSS-in-JS with newer React features like Server Components and Streaming requires library authors to support the latest version of React, including concurrent rendering We're working with the React team on upstream APIs to handle CSS and JavaScript assets with support for React Server Components and streaming architecture.
@ben-rogerson i think it's better this be mentioned somewhere in nextjs-styled-components tuts
Any update on this? I am also running into this issue with nextjs 14.
I'm currently migrating a Next 13 project from the
/pages
directory setup to the new/app
directory. As part of that it defaults to components being server components, and you must specify"use client"
at the top of a file to make it a client side component.As @ben-rogerson helpfully pointed out (π), there's a guide on how to use
styled-components
with this new setup, requiring a/lib/registry.ts
file.I got that working in another project (using the
/pages
directory), but when using with the/app
setup the initial"use client"
line is stripped out by the build process.To try to get to the bottom of it I've created a fresh next 13 install using
yarn create next-app --typescript
as detailed here.Then I went about following the guidance of integrating
withTwin
to allow for both SWC and webpack to run side by side, as shown here.Unfortunately I'm still seeing the build process remove the
"use client"
line, producing this error:I'm unsure how to get around this, as this is quite a minimal setup. I'm sure it's a simple config issue but I'm unsure which setting to tweak. With this being reproduced in a pretty vanilla project I thought this might also trip up others, and so an issue here benefit them too.
π The project reproducing this issue can be found here: https://github.com/fredrivett/next-13-use-client-issue