Example on how to make it work in a NX nextjs project in monorepo?
Hi! First of all, thanks for developing this cool library, we're facing some hydration errors that are being hard to debug, and hopefully this lib will help with that! We're facing an issue though, we use a NX monorepo, and we're not being able to setup this lib, as soon as we add withHydrationOverlay to the next.config.js file our application stops working.
This is how our next.config.js looks by default:
const {withNx} = require('@nx/next/plugins/with-nx')
const {withSentryConfig} = require('@sentry/nextjs')
const path = require('path')
const isAnalyzingBundle = process.env['ANALYZE'] === 'true'
const regexEqual = (x, y) => {
return (
x instanceof RegExp &&
y instanceof RegExp &&
x.source === y.source &&
x.global === y.global &&
x.ignoreCase === y.ignoreCase &&
x.multiline === y.multiline
)
}
/**
* @type {import('next').NextConfig}
*/
let nextConfig = {
// We should not increase it, otherwise we run out of Vercel's limits.
staticPageGenerationTimeout: 60,
swcMinify: true,
reactStrictMode: true,
distDir: 'dist',
pageExtensions: ['page.tsx', 'page.ts', 'api.tsx', 'api.ts'],
/*
Optional build-time configuration options
https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#extend-nextjs-configuration
*/
sentry: {
// This will be true by default on Sentry 8.0 so meanwhile we need to force its usage
hideSourceMaps: true,
},
webpack: (config) => {
// Adds rule in order to be able to use svgs using import, otherwise we get an error
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
resourceQuery: {not: [/url/]}, // exclude react component if *.svg?url
use: ['@svgr/webpack'],
})
/*
* Start of CSS modules classes output format configuration
* This entire section is needed just to change CSS modules
* classes output format as seen here:
* https://stackoverflow.com/a/71450423/10504792
*/
config.resolve.modules.push(path.resolve('./'))
const oneOf = config.module.rules.find(
(rule) => typeof rule.oneOf === 'object'
)
if (oneOf) {
// Find the module which targets *.scss|*.sass files
const moduleSassRule = oneOf.oneOf.find((rule) =>
regexEqual(rule.test, /\.module\.(scss|sass)$/)
)
if (moduleSassRule) {
// Get the config object for css-loader plugin
const cssLoader = moduleSassRule.use.find(({loader}) =>
loader.includes('css-loader')
)
if (cssLoader)
cssLoader.options = {
...cssLoader.options,
modules: {
...cssLoader.options.modules,
localIdentName: '[local]--[hash:base64:5]',
// You can also add other css-loader options here
},
}
}
}
return config
},
}
// Next js Bundle Analyzer https://www.npmjs.com/package/@next/bundle-analyzer
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: true,
})
const plugins = []
if (isAnalyzingBundle) {
plugins.push(withBundleAnalyzer)
}
module.exports = async (phase, context) => {
let updatedConfig = plugins.reduce((acc, fn) => fn(acc), nextConfig)
updatedConfig = await withNx(updatedConfig)(phase, context)
updatedConfig = withSentryConfig(updatedConfig)
return updatedConfig
}
We've tried to add the withHydrationOverlay in different ways, and all of them causes errors:
If we add withplugins.push(withHydrationOverlay) we get 404 for all pages.
If we add with
module.exports = async (phase, context) => {
let updatedConfig = plugins.reduce((acc, fn) => fn(acc), nextConfig)
updatedConfig = await withNx(updatedConfig)(phase, context)
updatedConfig = withSentryConfig(updatedConfig)
updatedConfig = withHydrationOverlay({})(updatedConfig)
return updatedConfig
}
we receive this warning: Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. and nothing works with a 500 (Internal Server Error). Tried moving the withHydrationOverlay to different places without success as well.
I know this is probably something related to NX, but maybe it's something you're interested to take a look and have some documentation around it?
I am completely unfamiliar with the Nx plugin and how it impacts adding other things, so I'm not sure what to suggest here.
Since the only changes made by the plugin is altering the webpack config, we could potentially extract a withHydrationOverlayWebpack(webpackConfig) function that you can provide inside of your webpack configuration directly.
In case having a webpack wrapper adds more flexibility: I released this https://github.com/BuilderIO/hydration-overlay/releases/tag/%40builder.io%2Freact-hydration-overlay%400.0.5
So you might be able to call that directly inside of your webpack config and hopefully that'll work?
Hi @samijaber! First off, major kudos to you for working on this! 🎉 Hydration errors are so frustrating to debug and I'm excited to use this library.
We also use an Nx monorepo with our Next.js app, and we ran into the same issue that Victor mentioned. We tried using the webpack wrapper, but it seems like the initial setup in hydration-overlay-initializer.js isn't happening soon enough, because we get this error:
Overlay line 46 is trying to access window.BUILDER_HYDRATION_OVERLAY.SSR_HTML, but it's undefined, which is why I'm assuming that setup script isn't running soon enough (though I could definitely be wrong there):
Our next config has the webpack plugin imported and it's being called in our webpack config section:
Any thoughts on what might be missing?
Thanks again for all your work! I'll keep poking at this but I'd appreciate any insight you might have!
Unfortunately, hard to debug this since it seems to be specific to Nx. Can you provide a small repro?
My instinct is that the initializer script is not running, or not running at the right time.
You can confirm that by adding a console.log to this file: node_modules/@builder.io/react-hydration-overlay/dist/hydration-overlay-initializer.js:
+ console.log('initializer script ran');
window.BUILDER_HYDRATION_OVERLAY = {};
window.addEventListener("error", (event)=>{
const msg = event.message.toLowerCase();
const isReactDomError = event.filename.includes("react-dom");
const isHydrationMsg = msg.includes("hydration") || msg.includes("hydrating");
if (isReactDomError && isHydrationMsg) {
window.BUILDER_HYDRATION_OVERLAY.ERROR = true;
let appRootEl = document.querySelector(window.BUILDER_HYDRATION_OVERLAY.APP_ROOT_SELECTOR);
if (appRootEl) {
window.BUILDER_HYDRATION_OVERLAY.CSR_HTML = appRootEl.innerHTML;
}
}
});
let BUILDER_HYDRATION_OVERLAY_ELEMENT = document.querySelector(window.BUILDER_HYDRATION_OVERLAY.APP_ROOT_SELECTOR);
if (BUILDER_HYDRATION_OVERLAY_ELEMENT) {
window.BUILDER_HYDRATION_OVERLAY.SSR_HTML = BUILDER_HYDRATION_OVERLAY_ELEMENT.innerHTML;
}
If this script doesn't run, then it means the webpack plugin is not adding the script correctly.
I dug into this a bit more today, and I noticed this in my console:
error - ../node_modules/@builder.io/react-hydration-overlay/dist/hydration-overlay-initializer.js (2:0) @ eval
error - unhandledRejection: ReferenceError: window is not defined
at eval (webpack-internal:///../../node_modules/@builder.io/react-hydration-overlay/dist/hydration-overlay-initializer.js:2:1)
at ../../node_modules/@builder.io/react-hydration-overlay/dist/hydration-overlay-initializer.js (/Users/H726042/git/eks-test-nsk-app/apps/ui/dist/.next/server/node_modules_builder_io_react-hydration-overlay_dist_hydration-overlay-initializer_js.js:20:1)
at __webpack_require__ (/Users/H726042/git/eks-test-nsk-app/apps/ui/dist/.next/server/webpack-runtime.js:33:42)
at __webpack_require__.t (/Users/H726042/git/eks-test-nsk-app/apps/ui/dist/.next/server/webpack-runtime.js:139:38)
null
It appears that the initializer script is indeed running, but on the server instead of the client.
Huh. I am not familiar enough with Nextjs internals to know why this is happening (ONLY for Nx). We should add a check for window in the script to avoid running on the server, but I am not sure if it will run on the client afterwards.
Would you be able to create a minimum reproduction codebase using Nx + Next + react-hydration-overlay that showcases your issue? To make sure it isn't caused by some other dependency in your project and add it to this project to make sure there are no regressions.
Could your issue be the same as https://github.com/BuilderIO/hydration-overlay/issues/25#issuecomment-1877521434 ?
I did a small proto here: https://github.com/belgattitude/flowblade/tree/main/examples/nextjs-app. It works with nx in a monorepo. Haven"t checked all but you can make a test
Check the nextjs.config.mjs
let nextConfig = //...
if (
process.env.NEXT_PUBLIC_HYDRATION_OVERLAY === 'yes' &&
process.env.NODE_ENV === 'development' &&
!process.env.TURBOPACK
) {
try {
const { withHydrationOverlay } = await import(
'@builder.io/react-hydration-overlay/next'
).then((mod) => mod);
nextConfig = withHydrationOverlay({})(nextConfig);
console.log(`- ${pc.green('info')} HydrationOverlay enabled`);
} catch {
// simply ignore
}
}
export default nextConfig;
The global app root layout
export default function RootLayout({
children,
}: Readonly<{
children: ReactNode;
}>) {
return (
<html lang="en">
<body className={`${fontInter.variable} antialiased`}>
<DevHydrationOverlayProvider>
{children}
</DevHydrationOverlayProvider>
</body>
</html>
);
}
And the provider
export const DevHydrationOverlayProvider: FC<Props> = (props) => {
const { children, integrations = defaultIntegrations } = props;
if (
process.env.NODE_ENV === 'development' &&
process.env.NEXT_PUBLIC_ENABLE_HYDRATION_OVERLAY !== 'true' &&
!process.env.TURBOPACK
) {
return <>{children}</>;
}
return (
<HydrationOverlay integrations={integrations}>{children}</HydrationOverlay>
);
};
Closing since we can't reproduce. feel free to reopen if issue persists