Nextjs styles from a shared library are not loaded on initial loading - reactjs

I have a Nextjs app and a shared UI library that is being used not only for Nextjs app but also other apps (SPAs). kinda like Material-ui / semantic-ui that has a list of UIs that can be used for my apps.
This shared library app was built only for SPA initially but now that I'd like to add a Nextjs app, I'm sure it needs some modification.
The styles on Nextjs app are rendered correctly on initial load since I followed the Next's example with React-jss on their repo. (all of my apps are using react-jss) https://github.com/vercel/next.js/blob/canary/examples/with-react-jss/pages/_document.js
_document.js
import Document from "next/document";
import { SheetsRegistry, JssProvider, createGenerateId } from "react-jss";
export default class JssDocument extends Document {
static async getInitialProps(ctx) {
const registry = new SheetsRegistry();
const generateId = createGenerateId();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => (
<JssProvider registry={registry} generateId={generateId}>
<App {...props} />
</JssProvider>
)
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
<style id="server-side-styles">{registry.toString()}</style>
</>
)
};
}
}
But once I load this shared library app on my Nextjs app, the styles from the shared library are not applied at all on initial load. like I did in _document.js, I'm sure I need to make some changes to accommodate this case where I load another library that has its own styles in either _document.js or webpack.
The error message I got looks something like this
Warning: Prop `className` did not match. Server: "wrapper-0-2-7 wrapper-d0-0-2-25 size_2-0-2-11" Client: "wrapper-0-2-7 wrapper-d2-0-2-27 size_2-0-2-11"
The way I connect my next app to this shared library app is to use webpack.
nextapp/config/aliases.js
const path = require("path");
module.exports = {
react: path.join(__dirname, "../node_modules/react"),
"react-jss": path.join(__dirname, "../node_modules/react-jss"),
};
nextapp/next.config.js
module.exports = {
reactStrictMode: true,
webpack: config => {
config.resolve.alias = {
...(config.resolve.alias || {}),
...aliases
};
config.resolve.modules = [
"node_modules",
resolveApp("node_modules"),
"../sharedLibrary/node_modules" <-------
];
config.module.rules.push({
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: [resolveApp("../b/src"), resolveApp("./")], <-------
// include: [paths.appSrc],
loader: require.resolve("babel-loader"),
options: {
customize: require.resolve("babel-preset-react-app/webpack-overrides"),
configFile: resolveApp("./.babelrc"),
plugins: [
[
require.resolve("babel-plugin-named-asset-import"),
{
loaderMap: {
svg: {
ReactComponent: "#svgr/webpack?-svgo,+titleProp,+ref![path]"
}
}
}
]
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: false // TODO
}
});
config.resolve.plugins.push(
PnpWebpackPlugin,
new ModuleScopePlugin(
[resolveApp("./"), resolveApp("../sharedLibrary/src")],
[
resolveApp("package.json"),
resolveApp("../sharedLibrary/package.json")
]
)
);
return config;
}
};
Could someone please help me how to render styles from this shared library on Nextjs app?
Thank you in advance

Related

Is it possible to mount entire Vue 2 application in React using module federation?

Hello I'm trying to mount Vue 2 app - generated by Vue CLI 5 - which has nothing inside only 2 routes created by CLI tool - my goal is to mount it in my host app which is React 17 app.
I'm wondering if it is possible ? I see that remoteEntry file is beeing fetched correctly by the host React app:
Request URL: http://localhost:8080/remoteEntry.js
Request Method: GET
Status Code: 200 OK
When I'm trying to mount it I'm stuck on following error:
error Error: Module "." does not exist in container.
while loading "." from webpack/container/reference/#zx-mfe/vueRemote
Here is my vue.config setup for module-federation
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
devServer: {
historyApiFallback: true
},
optimization: {
splitChunks: false
},
plugins: [
new ModuleFederationPlugin({
name: 'vueRemote',
filename: 'remoteEntry.js',
exposes: {
'./sharedApp': './src/expose.ts'
}
})
]
}
})
And the expose file for Vue
//app content
// export default new Vue({
// router,
// render: h => h(App)
// }).$mount('#app')
import app from './main'
export default app
And in React here is the module federation setup
new ModuleFederationPlugin({
name: "host",
remotes: {
"#zx-mfe/vueRemote": "vueRemote#http://localhost:8080/remoteEntry.js",
},
}),
And the Loader component where I would like to mount entire Vue app
import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
//#ts-ignore
const VueApp = React.lazy(() => import("#zx-mfe/vueRemote")
.then((res) => {
console.log('res', res)
})
.catch((error) => {
console.log('error', error)
}));
export const VueAppLoader = () => {
return (
<React.Suspense fallback="loading">
<ErrorBoundary fallback={<h2>Failed to load Vue Application</h2>}>
<VueApp />
</ErrorBoundary>
</React.Suspense>
)
}
I have seen that with Vue 3 it will be way easier, but I'm wondering if it will be possible to the same with Vue 2 to support older projects. Thank you for any advice 🙏

Unable to resolve path to module in nextJS app using module deferation plugin

I'm working with the module-federation/nextjs-mf webpack plugin, which allow us to work with micro-frontned architecture.
According with the official documentation and based on this example, we can share components between the remote and host app.
The above example works great using only out of the box nextjs code.
Actually I'm trying to achieve this, a host app and n remote apps all of them using:
NextJS
module-federation/nextjs-mf plugin
TypeScript
As you can see I'm working with more than 2 nextjs apps, the remotes apps can share components successfully, but, the host app is breaking due to this error:
Its implementation:
import dynamic from "next/dynamic"
const NextRemoteComponent = dynamic(
() => import("remote_finances/next-remote-component"),
{ ssr: false }
)
Unlike the rest of the apps, this host app is using a "different" next.config.js configuration:
module.exports = {
webpack5: true,
webpack(config, options) {
const { webpack, isServer } = options;
config.experiments = { topLevelAwait: true };
config.module.rules.push({
test: /_app.js/,
loader: '#module-federation/nextjs-mf/lib/federation-loader.js',
});
config.plugins.push(
new webpack.container.ModuleFederationPlugin({
remotes: {
remote_finances: "remote_finances#http://localhost:8081/_next/static/chunks/remoteEntry.js",
remote: 'remote#http://localhost:8081/_next/static/chunks/remoteEntry.js',
},
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: false,
},
},
}),
);
return config;
},
// nextJS config
reactStrictMode: true,
experimental: {
outputStandalone: true
},
env: {
BASE_URL : `https://qa-core-api.nordhen.com`,
},
};
I tryed almost everthing, changed and tryed many .eslintrc-.eslintrc.json configurations, packages, and don't know exactly if it's due to:
Typescript
Lint
Webpack
If you need any extra information about the code or its implementation you can ask.
Thanks for your time, I appreciate it.
Try to bypass eslint (and if you are using vs code) with this two lines.
It worked for me.
import dynamic from "next/dynamic"
const NextRemoteComponent = dynamic(
// #ts-ignore
// eslint-disable-next-line
() => import("remote_finances/next-remote-component"),
{ ssr: false }
)

Storybook can't find module '#reach/router'. But gatsby is working fine

Getting the error
Storybook can't find module '#reach/router'. Gatsby is working fine.
import * as React from "react";
import { useLocation } from "#reach/router";
const Header = () => {
const { pathname } = useLocation();
const isCheckoutHeader = pathname.includes("quote-process");
return (
<>
<div>code here</div>
</>
);
};
export default Header;
With Gatsby v4 you'll need to remap #reach/router to #gatsbyjs/reach-router. If using Storybook v6 and Webpack5 you can use the NormalModuleReplacementPlugin to perform this remapping.
This assumes your setup is similar to the one explained in the Gatsby docs: Visual Testing with Storybook.
// .storybook/main.js
const webpack = require("webpack")
module.exports = {
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.#(js|jsx|ts|tsx)"],
addons: [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/addon-interactions",
],
framework: "#storybook/react",
core: {
builder: "#storybook/builder-webpack5",
},
staticDirs: ["../public", "../static"],
webpackFinal: async config => {
// Transpile Gatsby module because Gatsby includes un-transpiled ES6 code.
config.module.rules[0].exclude = [/node_modules\/(?!(gatsby)\/)/]
// Use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook
config.module.rules[0].use[0].options.plugins.push(
require.resolve("babel-plugin-remove-graphql-queries"),
)
// remap #reach/router to fork included in Gatsby
config.plugins.push(
new webpack.NormalModuleReplacementPlugin(
/^#reach\/router/,
"#gatsbyjs/reach-router",
),
)
return config
},
}
This is because the purpose of Storybook is made to see the components and not for routing between the pages.

No syntax highlighting with React Monaco Editor

Just installed React Monaco Editor and seems to be functioning properly except there is no syntax highlighting. I'm trying to use "python" as the language but the font stays the same gray default colour:
Any ideas on how to correct the issue?
Here is my Code.js where I'm running the Monaco Editor:
import React from "react";
import MonacoEditor from "react-monaco-editor";
class Code extends React.Component {
constructor(props) {
super(props);
this.state = {
code: 'print("Hello, World!")'
};
}
editorDidMount(editor, monaco) {
console.log("editorDidMount", editor);
editor.focus();
}
onChange(newValue, e) {
console.log("onChange", newValue, e);
}
render() {
const code = this.state.code;
const options = {
selectOnLineNumbers: true,
fontSize: 18,
colorDecorators: true
};
return (
<MonacoEditor
width="800"
height="600"
language="python"
theme="vs-dark"
value={code}
options={options}
onChange={this.onChange}
editorDidMount={this.editorDidMount}
/>
);
}
}
export default Code;
Also I added this code to the top of webpack.config.js:
const path = require('path');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
plugins: [
new MonacoWebpackPlugin({
// available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options
languages: ['python']
})
]
};
const APP_DIR = path.resolve(__dirname, './src');
const MONACO_DIR = path.resolve(__dirname, './node_modules/monaco-editor');
module.exports = {
test: /\.css$/,
include: APP_DIR,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: true,
namedExport: true,
},
}],
}, {
test: /\.css$/,
include: MONACO_DIR,
use: ['style-loader', 'css-loader'],
}
If you are using the Monaco editor with create-react-app you will need a different approach, if you don't want to eject the app (to allow manually editing the webpack config file). This paragraph describes it pretty well:
The easiest way to use the react-monaco-editor with create-react-app is to use the react-app-rewired project. For setting it up, the following steps are required:
Install react-app-rewired: npm install -D react-app-rewired
Replace react-scripts by react-app-rewired in the scripts section of your packages.json
Create a config-overrides.js in the root directory of your project with the following content:
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = function override(config, env) {
config.plugins.push(new MonacoWebpackPlugin({
languages: ['json']
}));
return config;
}
For more information checkout the documentation of react-app-rewired
here.
I did not have to specify anything else to make it work. No need to specify loaders for webpack manually.
For me both of the above answers are not working - not sure if it's related to Codesandbox or I did a mistake.
But using #monaco-editor/react is working with-out any changes to the CRA setup.
The only difference in the usage is that the default export is not a controlled component - so onchange is not working.
To have a controlled component, just use import {ControlledEditor as MonacoEditor} from "#monaco-editor/react". The onchange handler needs to be slightly modified, first the event & then the newText - just a small difference in the implementation.
The usage looks like following:
import React, { useState } from "react";
import { ControlledEditor as MonacoEditor } from "#monaco-editor/react";
export const Editor = () => {
const [code, setCode] = useState(`const greeting = () => {
alert("Hello world");
}`);
const options = {
minimap: {
enabled: false
}
};
const changeHandler = (evt, newText) => {
setCode(newText);
};
const editorDidMount = (editor, monaco) => {
console.log("editorDidMount", editor);
};
return (
<MonacoEditor
width="100%"
height="100%"
language="javascript"
theme="vs-dark"
value={code}
options={options}
onChange={changeHandler}
editorDidMount={editorDidMount}
/>
);
};
The options can be used to modify the Monaco editor. In my case I don't want to display the minimap. All available options can be found in the editor api docs
You can find the working demo in this Codesandbox.
The only thing that I found that is not working is undo/redo as described in the following issue. No change event triggered but I'll check this later - for now I'm happy with it.
Did you fail to configure CSS for Monaco Editor in Webpack? Perhaps that's issue since everything else looks good.
const path = require('path');
const MONACO_DIR = path.resolve(__dirname, './node_modules/monaco-editor');
{
test: /\.css$/,
include: MONACO_DIR,
use: ['style-loader', 'css-loader'],
}
Solution
The Problem was nothing with the configuration but the place where it was configured.
To customize webpack in your React CRA boilerplate, you must eject the app or use customize-cra if you don't want to eject, and do the configuration. OP here configured webpack inside node-modules/, That's not the right to configure your webpack, like at all. Use react-app-rewired with customize-cra.

Warning: Prop `className` did not match. when using styled components with semantic-ui-react

I use this code to margin my Button from top:
const makeTopMargin = (elem) => {
return styled(elem)`
&& {
margin-top: 1em !important;
}
`;
}
const MarginButton = makeTopMargin(Button);
and whenever i use MarginButton node, I get this error: Warning: PropclassNamedid not match. Server: "ui icon left labeled button sc-bwzfXH MjXOI" Client: "ui icon left labeled button sc-bdVaJa fKCkqX"
You can see this produced here.
What should I do?
This warning was fixed for me by adding an .babelrc file in the project main folder, with the following content:
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
See following link for an example:
https://github.com/nblthree/nextjs-with-material-ui-and-styled-components/blob/master/.babelrc
Or you could just add this to your next.config.js. This also makes it so next-swc (speedy web compiler) works to reduce build times. See more here.
// next.config.js
module.exports = {
compiler: {
// Enables the styled-components SWC transform
styledComponents: true
}
}
You should install the babel plugin for styled-components and enable the plugin in your .babelrc
npm install --save-dev babel-plugin-styled-components
.babelrc
{
"plugins": [
[
"babel-plugin-styled-components"
]
]
}
The main reason I am posting this answer to help people understand the tradeoff. When we're using .babelrc in next project it's going to opt of SWC compiler which is based on Rust (Learn More).
It's going to show message something like this when you opt for custom bable config.
info - Disabled SWC as replacement for Babel because of custom Babel configuration ".babelrc"
I did more digging on this to only find out following! Ref
Next.js now uses Rust-based compiler SWC to compile
JavaScript/TypeScript. This new compiler is up to 17x faster than
Babel when compiling individual files and up to 5x faster Fast
Refresh.
So tradeoff was really huge, we can lose significant amout of performance. So I found a better solution which can solve this issue and keep SWC as default compiler.
You can add this experimental flag in your next.config.js to prevent this issue. Ref
// next.config.js
module.exports = {
compiler: {
// ssr and displayName are configured by default
styledComponents: true,
},
}
If you have already added babel plugins, delete the .next build folder & restart the server again
credit: Parth909 https://github.com/vercel/next.js/issues/7322#issuecomment-912415294
I was having the exact same issue and it was resolved by doing:
npm i babel-preset-next
npm install --save -D babel-plugin-styled-components
and adding this to .babelrc file:
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
Styled components server side rendering
Server Side Rendering styled-components supports concurrent server
side rendering, with stylesheet rehydration. The basic idea is that
everytime you render your app on the server, you can create a
ServerStyleSheet and add a provider to your React tree, that accepts
styles via a context API.
This doesn't interfere with global styles, such as keyframes or
createGlobalStyle and allows you to use styled-components with React
DOM's various SSR APIs.
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'
const sheet = new ServerStyleSheet()
try {
const html = renderToString(sheet.collectStyles(<YourApp />))
const styleTags = sheet.getStyleTags() // or sheet.getStyleElement();
} catch (error) {
// handle error
console.error(error)
} finally {
sheet.seal()
}
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
const sheet = new ServerStyleSheet()
try {
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<YourApp />
</StyleSheetManager>
)
const styleTags = sheet.getStyleTags() // or sheet.getStyleElement();
} catch (error) {
// handle error
console.error(error)
} finally {
sheet.seal()
}
In my case as im using nextjs
import Document, { Head, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
const sheet = new ServerStyleSheet();
const page = renderPage(App => props =>
sheet.collectStyles(<App {...props} />)
);
const styleTags = sheet.getStyleElement();
return { ...page, styleTags };
}
render() {
return (
<html>
<Head>{this.props.styleTags}</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
I have solved this issue following these steps.
Create a file named .babelrc in the root directory and configure the .babelrc file.
delete the .next build folder(It stores some caches).
Restart the server.
Hot reload the browser.
.babelrc configuration file
{
"presets": [
"next/babel"
],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
]
}
PropType errors are runtime errors that will let you know that the data expected being passed to a prop is not what is expected. It looks like the className prop that is being set on your component is not the same when the component is rendered on the server and when it is then rendered in the client's DOM.
Since it looks like you are using server side rendering, you need to make sure that your class names are deterministic. That error is showing you the class that is being created by your styled-components library on the server and how it is different from the DOM. For libraries that do not normally have deterministic class names, you need to look at advanced configurations. Take a look at the styled-components documentation regarding specificity as it pertains to SSR.
//1. I got an error when using material-ui with Next.js
/********************************************* */
//2. The code I imported was like this :
const useStyles = makeStyles({
root: { // root must change
width: 100 ,
}
});
const Footer = () => {
const classes = useStyles();
return (
<div className={classes.root} > { /* root must change */}
<p> footer copyright #2021 </p>
</div>
)
}
export default Footer;
/********************************************* */
//3. I changed the code like this :
const useStyles = makeStyles({
footer: { // changed here to footer
width: "100%",
backgroundColor: "blue !important"
}
});
const Footer = () => {
const classes = useStyles();
return (
<div className={classes.footer} > { /* changed here to footer */}
<p> footer copyright #2021 </p>
</div>
)
}
export default Footer;
// I hope it works
For Old versions form Nextjs < 12, Go to next.config.js file and add this line inside nextConfig object:
experimental: {
// Enables the styled-components SWC transform
styledComponents: true
}
for new NextJs above 12:
compiler: {
styledComponents: true
}
if that does not work you need to make an NO SSR component wrapper like this:
// /components/NoSsr.js
import dynamic from 'next/dynamic'
const NoSsr = ({ children }) => <>{children}</>
export default dynamic(() => Promise.resolve(NoSsr), { ssr: false })
Then you need to add warp No SSR with your component like this:
// /pages/index.js
import NoSsr from '../components/NoSsr'
import CircleButton from '../components/buttons/CircleButton'
const HomePage = () => {
return (
<>
<p>Home Page Title</p>
<NoSsr>
{/* Here your styled-component */}
<makeTopMargin ele={...} />
</NoSsr>
</>
)
}
I'm using NextJS 12 and encountered the same issue, well error in the console, code was working ok.
I fixed it by creating a .babelrc file at the root of the project and add:
{
"presets": [
"next/babel"
],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
]
}
Styled Components have full, core, non-experimental support in Next now (2022), but you have to turn them on:
Add the following to your next.config.js:
compiler: {
styledComponents: true,
},
My full, mostly vanilla, next.config.js now looks like this:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
compiler: {
// Enables the styled-components SWC transform
styledComponents: true,
},
}
module.exports = nextConfig
https://nextjs.org/blog/next-12-1#improved-swc-support
I followed all the other advice, around setting up .babelrc (or .babelrc.js), but noticed this message in the Next.js docs:
When css-in-js libraries are not set up for pre-rendering (SSR/SSG) it will often lead to a hydration mismatch. In general this means the application has to follow the Next.js example for the library. For example if pages/_document is missing and the Babel plugin is not added.
That linked to this file, showing that I needed to add this to pages/_document.tsx to:
// if you're using TypeScript use this snippet:
import React from "react";
import Document, {DocumentContext, DocumentInitialProps} from "next/document";
import {ServerStyleSheet} from "styled-components";
export default class MyDocument extends Document {
static async getInitialProps(
ctx: DocumentContext,
): Promise<DocumentInitialProps> {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
}
A blog post by Raúl Sánchez also mentions this solution, linking to the JavaScript version if you're not using TS (pages/_document.js):
// if you're *not* using TypeScript use this snippet:
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
If you are using create-react-app, you can use thi solution.
File called styled.ts
import styled from 'styled-components/macro';
import { css } from 'styled-components';
export const ListRow = styled.div`
...
...
`
Based on the files name, the prefix will be as following.
`${file_name}__{styled_component_name} ${unique_id}`
Meaning when implemented it will have the following classname
Although it would be nice to specify from where the first prefix would be taken from, meaning instead of file_name, we take folder_name. I currently dont know the solution for it.
To expand on C. Molindijk's answer, this error occurs when server-side class is different from client-side because styled-components generates its own unique class Id's. If your Next app is server-side rendered, then his answer is probably correct. However, Next.Js is by default statically generated, so unless you enabled SSR, configure it like this without ssr set to true:
{
"presets": ["next/babel"],
"plugins": [["styled-components"]]
}
This answer is for those who are using NextJs version > v12.0.1 and SWC compiler. You don't have to add _document.js file nor do babel related stuff anymore since it has been replaced by SWC compiler since v12.0.0. Only that your next.config.js file should look like the following since NextJs supports styled components after v12.1.0 and restart the server and it should work: more here
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
// add the following snippet
compiler: {
styledComponents: true,
},
};
module.exports = nextConfig;

Resources