NextJS wrong CSS order on production build - javascript

I import CSS files from local files and node modules:
//> Global Styling
// Local
import "../styles/globals.scss";
// Icons
import "#fortawesome/fontawesome-free/css/all.min.css";
// Bootstrap
import "bootstrap-css-only/css/bootstrap.min.css";
// Material Design for Bootstrap
import "mdbreact/dist/css/mdb.css";
This works as intended on my local development version. All styles appear as they should be.
As you can see here, the styling is different on local and production.
(Take a look at font and buttons)
(Development left, Production right)
My next.config.js is:
//#region > Imports
const withSass = require("#zeit/next-sass");
const withCSS = require("#zeit/next-css");
const withFonts = require("next-fonts");
const withImages = require("next-images");
const withPlugins = require("next-compose-plugins");
//#endregion
//#region > Exports
module.exports = [
withSass({
webpack(config, options) {
config.module.rules.push({
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: "url-loader",
options: {
limit: 100000,
},
},
});
return config;
},
}),
withPlugins([withCSS, withFonts, withImages]),
];
//#endregion
/**
* SPDX-License-Identifier: (EUPL-1.2)
* Copyright © 2020 InspireMedia GmbH
*/
It seems the MDB styling is being overwritten by bootstrap on building the app. I deploy my app by using next build && next export && firebase deploy and use the ./out folder for deployment source.
You can find the code here: https://github.com/aichner/nextjs-redux-template

If the issue is incorrect styling. (as you are using material-ui) :
Create _document.js under pages directory.
Fill the file with following code :
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "#material-ui/styles"; // works with #material-ui/core/styles, if you prefer to use it.
import theme from "../Theme"; // change this theme path as per your project
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* Not exactly required, but this is the PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
Reason : Material UI uses context behind the scenes to apply its styling. Due to NextJs server side rendering, this context will be lost. So, we need to tell Next to make use of that previous context. The above code does that.

Related

Lazy import SCSS/CSS in React component

I'm using React lazy with Suspense to successfully code split my JS. Below mock up simply illustrates how I'm doing it.
const QuickView = React.lazy(() =>
import('quickview'),
);
<Suspense fallback={<></>}>
<QuickView/>
</Suspense>
However I have noticed that my styles have been chunked into a separate sheet containing all lazy components CSS. This sheet is then loaded immediately on page load instead of individually when the components JS is loaded.
I am using next 10.2.3 (working towards updating) with the below next.config.js
module.exports = withTM(
withCSS(
withSass({
cssModules: true,
cssLoaderOptions: {
importLoaders: 1,
localIdentName: '[local]___[hash:base64:5]',
},
assetPrefix,
transpileModules: [
'#web-core-sdk',
'#web-component-quickview',
'#web-component-library',
],
webpack(config) {
config.module.rules.forEach((rule) => {
if (String(rule.test) === String(/\.css$/)) {
rule.use.forEach((u) => {
if (u.options) {
u.options.modules = false;
}
});
}
});
return config;
},
}),
),
);
My ideal outcome is that the styles are individually chunked and only downloaded when the components js is loaded.
I have tried dynamically importing the stylesheet itself using below. However it seems as though if the import is reachable the chunk at page load will contain the styles instead of loading them when the import is called.
useEffect(() => {
if (!hasMountedQuickView) return;
(async () => {
await import('./QuickView.module.scss');
})();
}, []);
Thank you for any help

Unable to do Conditional import of file in React app

I am having issues importing a JSON file depending on a process.env.WORLD value in my React Typescript app. This is done in a .tsx file defining a React Context, and no React components are used here.
The JSON file can be loaded without problems when we ignore the process.env variable.
import data from '../main.json';
TS file loading the JSON file: src/contexts/world.tsx
JSON file: src/main.json
In my first attempt, I tried
let file;
if (process.env.WORLD == 'main') {
file = '../main.json';
} else {
file = '../test.json';
}
import data from file;
but got the error
Import in body of module; reorder to top import/first
Tried using dynamic import
const data = await import(
process.env.WORLD == 'main'
? '../main.json'
: '../test.json'
);
but my setup does not allow a top-level await
Module parse failed: Cannot use keyword 'await' outside an async function
Tried using require instead of import
let file;
if (process.env.WORLD === 'main') {
file = '../main.json';
} else {
file = '../test.json';
}
const data = require(file);
but it almost works except that the file cannot be found
contexts sync:2 Uncaught Error: Cannot find module '../main.json'
Strangely, there is no problem using the same file path with import as shown in the first example in this question.
Typescript target is ES2018, and React 18 is used.
Why is the file not found when using require, and how can we solve this problem?
In a comment, you added information which implies that the external JSON data is being used as part of the value of a ContextProvider.
By fetching (or importing) the JSON data dynamically just before render, you can pass it via props to your context provider on the initial render, then memoize it in a ref. Below is a working example in a Stack Overflow snippet which uses object URLs in place of your local file specifiers.
Note that there are many considerations involved in how to optimize external data loading, and your question does not provide much detail regarding your scenario, but the approach that works best for your app will depend on those details. In any case, what you've described in the question would be better handled by re-writing the contents of a single JSON file on disk (based on the environment variable) before building or running your app each time. This would need to be handled outside the React app by an external process (like an npm script, for example), and would allow you to avoid the scenario entirely, while also optimizing the build result with static imports.
TypeScript Playground
<div id="root"></div><script src="https://unpkg.com/react#18.2.0/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#18.2.0/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.18.8/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
// import ReactDOM from 'react-dom/client';
// import {
// default as React,
// createContext,
// StrictMode,
// useContext,
// useRef,
// type ReactNode,
// type ReactElement,
// } from 'react';
// This Stack Overflow snippet demo uses UMD modules
// instead of the commented import statments above
const {
createContext,
StrictMode,
useContext,
useRef,
} = React;
type JsonData = {
items: string[];
listTitle: string;
};
// An initial value is not required as long as this
// is not used outside the context provider:
const jsonContext = createContext(undefined as unknown as JsonData);
function JsonProvider ({children, data}: {
children: ReactNode;
data: JsonData;
}): ReactElement {
const ref = useRef(data);
return (
<jsonContext.Provider value={ref.current}>
{children}
</jsonContext.Provider>
);
}
function App (): ReactElement {
const {items, listTitle} = useContext(jsonContext);
return (
<div>
<h1>{listTitle}</h1>
<ul>
{items.map((item, index) => (<li key={index}>{item}</li>))}
</ul>
</div>
);
}
type JsonPrimitive = boolean | null | number | string;
type JsonSerializable = JsonPrimitive | JsonSerializable[] | { [key: string]: JsonSerializable };
function createJsonObjectUrl (serializable: JsonSerializable): string {
const json = JSON.stringify(serializable);
const blob = new Blob([json], {type: 'application/json'});
return URL.createObjectURL(blob);
}
async function inititalRender () {
// The Stack Overflow code snippet sandbox doesn't have Node's `process`,
// so we'll simulate it here:
const process = {
env: {
// Simulate that it could be either "main" or "test"
REACT_APP_JSON_FILE: Math.random() < 0.5 ? 'main' : 'test',
} as Partial<Record<string, string>>,
};
// In your app, this would simply be "../main.json" or "../test.json",
// but the Stack Overflow code snippet sandbox doesn't have access to
// your local files, so we'll simulate the specifier with an object URL
// (which can also be fetched/imported):
const specifier = process.env.REACT_APP_JSON_FILE === 'main'
? createJsonObjectUrl({
items: ['a', 'b', 'c'],
listTitle: 'main',
})
: createJsonObjectUrl({
items: ['one', 'two', 'three'],
listTitle: 'test',
});
// Use fetch to get the data (we're in an async function, so that's OK!)
const data = await (await fetch(specifier)).json();
// Or (in the future) dynamic import with an assertion:
// const {default: data} = await import(specifier, {assert: {type: 'json'}});
const reactRoot = ReactDOM.createRoot(document.getElementById('root')!);
reactRoot.render(
<StrictMode>
<JsonProvider {...{data}}>
<App />
</JsonProvider>
</StrictMode>
);
}
inititalRender();
</script>
Could importing both files then choose the right file be a fit for you ?
import main from '../main.json';
import test from '../test.json';
let file;
if (process.env.WORLD == 'main') {
file = main;
} else {
file = test
}
or use react.lazy :
const Questions = React.lazy(() => {
if (process.env.WORLD == 'main') {
import('../main.json'))
} else {
import('../test.json'))
}
}

import npm modules asynchronously in react.js

I am using parcel as a bundler in React.js project.
How to load npm modules asynchronously in react.js?
There is only one page that uses one specific npm module so I didn't need to load it at first loading.
By avoiding this, I would like to reduce the bundle size.
Could you let me the proper way to do this?
========================
And also, if I understood anything wrongly about the bundle size optimization and lazy loading, please let me know.
By using Dynamic Import you may import the package when you really need the package.
You can use a dynamic import inside an useEffect hook like:
const Page = (props) => {
useEffect(
() => {
const [momentjsPromise, cancel] = makeCancelable(import("moment"));
momentjsPromise.then((momentjs) => {
// handle states here
});
return () => {
cancel?.();
};
},
[
/* don't forget the dependencies */
],
);
};
You can use dynamic imports.
Let's say you want to import my-module:
const Component = () => {
useEffect(() => {
import('my-module').then(mod => {
// my-module is ready
console.log(mod);
});
}, []);
return <div>my app</div>
}
Another way is to code-splitt Component itself:
// ./Component.js
import myModule from 'my-module';
export default () => <div>my app</div>
// ./App.js
const OtherComponent = React.lazy(() => import('./Component'));
const App = () => (
<Suspense>
<OtherComponent />
<Suspense>
);
my-module will be splitted along with Component.
These two patterns should work with any bundler, but it will work client side only.

How do I use Material UI with Next.js?

I am looking to use Next.js with Material UI with my React app. I have followed the official Material UI Next.js exmaple.
Although, when I try either run next dev or next build, the following error occurs.
I have run npm ls react to ensure I only have one version of react, and confirmed that react-dom and react were the same version (6.13.1).
It seems to be treating the originalRenderPage as a hook. Although, the official Next.js documentation also details the custom document page and has the same originalRenderPage within the class component.
I have tried converting the _document.js component into a functional component rather than a class, but it does not work.
The contents of the _document.js page (directly from the Material UI/Next.js example above) are:
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "#material-ui/core/styles";
import theme from "../components/theme";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};

Conditional imports in React Native

I'm having a bit of trouble getting conditional imports working in react native.
I have some files that are used in a react web app and in react native.
What I'd like:
if(process.env.REACT_PLATFORM === 'WEB') {
import('some_file').then(({someFunc})=> someFunc())
}
Because 'some_file' imports react_router.
However, this import is still happening, and the RN metro bundler throws
UnableToResolveError: Unable to resolve module 'react-router' from 'some_file'.
Even If I replace it as:
if(false) {
import('some_file').then(({someFunc})=> someFunc())
}
It still trys to load some_file. Is there anyway to only import/require this file if a condition is met?
Cheers!
EDIT:
Things I've tried:
Require instead of import.
https://babeljs.io/docs/plugins/syntax-dynamic-import/
Platform specific imports;
You can place the import in a component with the native.js extension and it will only be bundled for mobile (ios/android). e.g. MyComponent.native.js Then you have a component for web with the same name but the .js extension. e.g. My Component.js
When you import MyComponent from './components/MyComponent', the correct one will be imported and the other ignored.
After a bit of searching, in turns out dynamic imports can be a bit of a pain.
This is the solution I came up with, I've tried it in node.
const MODULE_NAME = <CONDITION> ? require(MODULE_A) : require(MODULE_B);
Alternatively, I guess you could do something like this;
const MODULE_TO_IMPORT = 'MODULE_IMPORT_STRING';
const MODULE_NAME = import(MODULE_TO_IMPORT).then(({someFunc}) => someFunc());
But the problem is that these require a module to be imported either way.
For React-navive-web we can use manage code for platform-specific that will manage in Mobile App and Web Also
Web-specific code # Minor platform differences can use the Platform
module.
import { Platform } from 'react-native';
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100,
});
For example, with the following files in your project:
MyComponent.android.js
MyComponent.ios.js
MyComponent.web.js
And the following import:
import MyComponent from './MyComponent';
React Native will automatically import the correct variant for each specific target platform.
Platform specific imports are nice but won't help you on the web.
react-native section in package.json is your friend:
"react-native": {
"module1": false,
"module2": "module3"
}
with this setup
// module1
export const x = 1
// module2
export const x = 2
// module3
export const x = 3
// will result in
import {x} from 'module1'
console.log( x === undefined ) // in the react-native environment
console.log( x === 1 ) // in the browser
import {x} from 'module2'
console.log( x === 3 ) // in the react-native environment
console.log( x === 2 ) // in the browser
import {x} from 'module3'
console.log( x === 3 ) // in the react-native environment
console.log( x === 3 ) // in the browser
The docs can be found here. It's for the browser section, but the react-native section works the same way.
I came across a problem where the project I am working on is using react-native-tvos and I tried to add react-native-ad-manager as a dependency but it doesn't support tvOS so I wanted to dynamically import the ad manager dependency for non tvOS platforms. I was able to get it to work like such:
import {Platform} from 'react-native';
const NullComponent = (props: any) => null;
const AdComponent = () => {
const [Banner, setBanner] = React.useState(() => NullComponent);
if (!Platform.isTV) {
import('react-native-ad-manager')
.then(({Banner: AdBanner}) => {
setBanner(() => AdBanner);
})
}
return (
<Banner />
)
}

Categories