ReferenceError: document is not defined when refresh nextjs page - reactjs

i am trying to create a simple UI library using react for Nextjs 9.4, here what i am doing
// input.js in React UI Lib
import React from "react";
import styled from "./input.module.scss";
const Input = React.forwardRef((props, ref) => (
<>
{props.label && <label className={styled.label}>{props.label}</label>}
<input className={styled.input} {...props} ref={ref} />
</>
));
export default Input;
and made an index to export all modules for simplicity
// app.js the index file for the lib
import PrimaryButton from "./components/button/primaryButton";
import TextInput from "./components/input/input";
import PasswordInput from "./components/passwordInput/password";
import CheckBox from "./components/checkbox/checkbox";
export {
PrimaryButton,
TextInput,
PasswordInput,
CheckBox
};
also here is my webpack config to build for SSR Next
const path = require("path");
const autoprefixer = require("autoprefixer");
const nodeExternals = require("webpack-node-externals");
const CSSLoader = {
loader: "css-loader",
options: {
modules: "global",
importLoaders: 2,
sourceMap: false,
},
};
const CSSModlueLoader = {
loader: "css-loader",
options: {
modules: true,
importLoaders: 2,
sourceMap: false,
},
};
const PostCSSLoader = {
loader: "postcss-loader",
options: {
ident: "postcss",
sourceMap: false,
plugins: () => [autoprefixer()],
},
};
const SassLoader = {
loader: "sass-loader",
options: {
// Prefer `dart-sass`
implementation: require("sass"),
},
};
module.exports = {
target: "node",
entry: "./src/app.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
chunkFilename: "[id].js",
publicPath: "",
library: "",
libraryTarget: "commonjs",
},
externals: [nodeExternals()],
resolve: {
extensions: [".js", ".jsx"],
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/,
},
{
test: /\.(sa|sc|c)ss$/i,
exclude: [/node_modules/, /\.module\.(sa|sc|c)ss$/i],
use: ["style-loader", CSSLoader, PostCSSLoader, SassLoader],
},
{
test: /\.module\.(sa|sc|c)ss$/i,
exclude: /node_modules/,
use: ["style-loader", CSSModlueLoader, PostCSSLoader, SassLoader],
},
{
test: /\.(png|jpe?g|gif)$/,
loader: "url-loader?limit=10000&name=img/[name].[ext]",
},
],
},
};
1-i build
2-publush on npm
3-import in Nextjs
then everything works well , but the problem is when i try to refresh (F5) the page during development i get the error
Unhandled Runtime Error
ReferenceError: document is not defined
how can i fix that ?

try to render component only in client side you can do with:
typeof window !== 'undefined' ? <Component /> : null
you are using style-loader in your webpack config, it will inject styles into head using document.createElement that is not availabe in SSR, you can choose other options like mini-css-extract-plugin

const Example = dynamic( () => import('example'), { ssr: false } )
https://github.com/elrumordelaluz/reactour/issues/130

try to check component in client side rendering ex:
const isBrowser = typeof window !== 'undefined';
isBrowser ? <Component/> : null;
another option is try to render using ssr false:
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
Thanks..

You may not always want to include a module on server-side. For example, when the module includes a library that only works in the browser.
Take a look at the following example:
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home

Related

Issues with style-loader lazyloading for multiple elements in shadow-dom

I'm currently working with webpack and style-loader to inject my styles into components that use shadow-dom. However, the issue happens when I try to use multiple instances of the element (the styles stop injecting). I was able to properly solve this issue with another component by adding the unuse function to my disconnectCallback for that component. Unfortunately, for this component below, I expect it to appear multiple times at once on a page. Am I doing something wrong?
Component.tsx:
import React from 'react';
import { createRoot } from 'react-dom/client';
// Styles
import styles from '../styles/contentScript.css';
// Images
const icon = chrome.runtime.getURL('assets/icon.png');
export default class CustomButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Functions
this.handleShow = this.handleShow.bind(this);
}
handleShow = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
...
};
connectedCallback() {
// Inject Styles
styles.use({ target: this.shadowRoot });
// Inject Component into ShadowRoot
createRoot(this.shadowRoot).render(this.render());
}
render() {
return (
<div className='absolute inset-0'>
<button className='main-button group' onClick={this.handleShow}>
<img src={icon} className='w-7' />
<span className='main-button-tooltip group-hover:scale-100'>
Open Popup
</span>
</button>
</div>
);
}
disconnectedCallback() {
styles.unuse();
}
}
customElements.define('custom-button', CustomButton);
webpack.config.js
// Imports...
module.exports = {
entry: {
...
},
module: {
rules: [
{
use: 'ts-loader',
test: /\.tsx?$/,
exclude: /node_modules/,
},
{
use: [
{
loader: 'style-loader',
options: {
injectType: 'lazyStyleTag',
insert: function insertIntoTarget(element, options) {
var parent = options.target || document.head;
parent.appendChild(element);
},
},
},
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
indent: 'postcss',
plugins: [tailwindcss, autoprefixer],
},
},
},
],
test: /\.css$/i,
},
{
type: 'asset/resource',
test: /\.(png|jpg|jpeg|gif)$/i,
},
]
},
resolve: {
...
},
plugins: [
...
],
output: {
filename: '[name].js',
clean: true,
},
optimization: {
...
},
}
I should also note (in case it's important) I am using tailwind for styling so I've included postcss and autoprefixer. This is also for a chrome extension so I'm creating this component in my contentScript. I have also tried it without the unuse call in my disconnectCallback and faced the same issue.
to follow the discussion on github, if you're only browser target is Chrome, then I really suggest you to use the CSSStyleSheet class.
In that case, you should drop from your webpack configuration the style-loader, as it's not needed anymore:
{
loader: 'style-loader',
options: {
injectType: 'lazyStyleTag',
insert: function insertIntoTarget(element, options) {
var parent = options.target || document.head;
parent.appendChild(element);
},
},
},
Then modify the configuration of the css-loader to have the option exportType = "css-style-sheet" (https://webpack.js.org/loaders/css-loader/#exporttype).
In this way, the exported element is already an object of type CSSStyleSheet, and you can use it directly on your web component:
import sheet from "./styles.css" assert { type: "css" };
document.adoptedStyleSheets = [sheet];
shadowRoot.adoptedStyleSheets = [sheet];
Normally, the 'postcss' step should not pose problems.
No need anymore to use "use / unuse" then (because that's an API of style-loder that you should remove with this solution)

Nrwl Storybook React Not Rending Images

I have a react project built with Nrwl. I just noticed that all the images in my components are not loading. I researched a bit tried different techniques but couldn't fix it. I have these files for example
libs/src/lib/components/Nav/Nav.tsx
import React, { FC, MouseEvent } from "react";
import NavStyled from "./Nav.style";
export const Nav: FC<NavProps> = ({ click }: NavProps) => {
return (
<NavStyled>
<div className="nav__container">
<a className="logo" href="/">
<img src="../../assets/icons/logo.svg"/>
</a>
</div>
</NavStyled>
);
};
export default Nav;
libs/src/lib/components/Nav/Nav.stories.tsx
import React from "react";
import Nav from "./Nav";
export default {
component: Nav,
title: "Components/Nav"
};
export const defaultStory = () => <Nav />;
libs/storybook/.storybook/webpack/config.js
const rootWebpackConfig = require("../../../.storybook/webpack.config");
// Export a function. Accept the base config as the only param.
module.exports = async ({ config, mode }) => {
config = await rootWebpackConfig({ config, mode });
config.resolve.extensions.push(".tsx");
config.resolve.extensions.push(".ts");
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve("babel-loader"),
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript"
]
}
});
return config;
};
.storybook/webpack.config.js
module.exports = async ({ config, mode }) => {
config.module.rules.push({
test: /\.stories\.tsx?$/,
loaders: [
{
loader: require.resolve("#storybook/source-loader"),
options: { parser: "typescript" }
}
],
enforce: "pre"
});
return config;
};
and I run the storybook using
nx run storybook:storybok
I think you're not using any image loader, you can try this loaded by adding this to your webpack config file.
const svgToMiniDataURI = require('mini-svg-data-uri');
const rootWebpackConfig = require("../../../.storybook/webpack.config");
// Export a function. Accept the base config as the only param.
module.exports = async ({ config, mode }) => {
config = await rootWebpackConfig({ config, mode });
config.resolve.extensions.push(".tsx");
config.resolve.extensions.push(".ts");
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve("babel-loader"),
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript"
]
},
test: /\.(png|svg|jpg|gif)$/,
loader: 'url-loader',
options: {
limit: true,
generator: content => svgToMiniDataURI(content.toString()),
},
});
return config;
};

Obfuscate React classNames in existing codebase

Hi guys
I am working on a big React application with an existing code-base (100+ components). Currently, we are using the traditional styling method, example:
JSX:
<div className="div" />
SCSS:
.div {
/* ... */
}
We are using webpack with these loaders:
/* ... */
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: { implementation: sass }
}
]
/* ... */
Is there any way to obfuscate these classNames from both JSX and SCSS without rewriting the whole thing? Is it also possible to rewrite only some components to be obfuscated or do we have to rewrite it all?
Thanks.
css-loader has a modules option which when set to true obfuscates class names. Unfortunately, this will obfuscate all of the class names and will require you to change all usages of <div className="div" /> to:
import styles from <stylesheet>;
<div className={styles.div} />
Edit:
The only way I can think of avoiding having to change all of the usages at once is by splitting your css config in webpack with two patterns, for example:
{
test: /.*dirA\/.*\.css/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader', options: { modules: true } }, ...]
},
{
test: /.*dirB\/.*\.css/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }, ...]
}
Solution for ejected Create-react-app
Example classNames: a_a, a_b, a_c .... etc.
Eject app
Install incstr
Create getScopedName.js at the config folder.
const incstr = require('incstr');
const createUniqueIdGenerator = () => {
const uniqIds = {};
const generateNextId = incstr.idGenerator({
alphabet: 'abcefghijklmnopqrstuvwxyzABCEFGHJKLMNOPQRSTUVWXYZ',
});
return (name) => {
if (!uniqIds[name]) {
uniqIds[name] = generateNextId();
}
return uniqIds[name];
};
};
const localNameIdGenerator = createUniqueIdGenerator();
const componentNameIdGenerator = createUniqueIdGenerator();
module.exports = (localName, resourcePath) => {
const componentName = resourcePath
.split('/')
.slice(-2, -1)[0];
const localId = localNameIdGenerator(localName);
const componentId = componentNameIdGenerator(componentName);
return `${componentId}_${localId}`;
};
Now...
Open the webpack.config.js and add:
const getScopedName = require('./getScopedName')
Find the ~445-460 rows and replace with:
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
...(isEnvDevelopment ? {
localIdentName: '[path]_[name]_[local]',
} : {
getLocalIdent: (context, localIdentName, localName) => (
getScopedName(localName, context.resourcePath)
),
})
},
}),
}
It's all. :)

React - kendo-ui styles module failed to parse - You may need an appropriate loader to handle this file type

I need to import default Kendo-ui template styles from this site https://www.telerik.com/kendo-react-ui/components/styling/.
When it trying to load styles, it throws NodeInvocationException: Prerendering failed because of error: Error: Module parse failed: "project folder"\node_modules\#progress\kendo-theme-default\dist\all.css Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
Here is my layout.ts file where I want to import style template
import * as React from 'react';
import { NavMenu } from './NavMenu';
import "#progress/kendo-theme-default/dist/all.css";
export class Layout extends React.Component<{}, {}> {
public render() {
return <div className='container-fluid'>
<div className='row'>
<div className='col-sm-3'>
<NavMenu />
</div>
<div className='col-sm-9'>
{ this.props.children }
</div>
</div>
</div>;
}
}
webpack.config looks like this, its default configuration from auto generated react-redux asp.net project
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const merge = require('webpack-merge');
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
// Configuration in common to both client-side and server-side bundles
const sharedConfig = () => ({
stats: { modules: false },
resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [new CheckerPlugin()]
});
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig(), {
entry: { 'main-client': './ClientApp/boot-client.tsx' },
module: {
rules: [
{ test: /\.css$/, use: ExtractTextPlugin.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) }
]
},
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new ExtractTextPlugin('site.css'),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin()
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig(), {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot-server.tsx' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
],
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
In order for the external .css files to work ( like kendo-theme-default coming from node_modules ), a postcss-loader is required.

How to use CSS Modules with webpack in React isomorphic app?

I am build an isomorphic app using react, react-router, express and webpack. Now I want to use css modules to import css.
I use import './index.css' in index.jsx, it works fine on client, but doesn't work on server rendering. The error is Error: Cannot find module './index.css'.
components/index.jsx
import React, {Component, PropTypes} from 'react';
import style from './index.css';
class App extends Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<div id="login">
// ...
</div>
);
}
};
export default App;
server/router/index.js
import url from 'url';
import express from 'express';
import swig from 'swig';
import React from 'react';
import {renderToString} from 'react-dom/server';
import {match, RouterContext} from 'react-router';
import routes from '../../client/routes/routes';
import DataWrapper from '../../client/container/DataWrapper';
import data from '../module/data';
const router = express.Router();
router.get('*', async(req, res) => {
match({
routes,
location: req.url
}, async(error, redirectLocation, props) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.status(302).redirect(redirectLocation.pathname + redirectLocation.search);
} else if (props) {
let content = renderToString(
<DataWrapper data={data}><RouterContext {...props}/></DataWrapper>
);
let html = swig.renderFile('views/index.html', {
content,
env: process.env.NODE_ENV
});
res.status(200).send(html);
} else {
res.status(404).send('Not found');
}
});
});
export default router;
webpack.config.dev.js(for webpack-dev-server)
var webpack = require('webpack');
var config = require('./config');
module.exports = {
devtool: 'inline-source-map',
entry: [
'webpack-dev-server/client?http://localhost:' + config.webpackPort,
'webpack/hot/only-dev-server',
'./src/client/entry',
],
output: {
path: __dirname + '/public/js',
filename: 'app.js',
publicPath: 'http://localhost:' + config.webpackPort + '/public/js',
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify('development')
}
})
],
resolve: {
extensions: ['', '.js', '.jsx', '.css']
},
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'react-hot',
exclude: /node_modules/
}, {
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/
}, {
test: /\.css$/,
loader: 'style-loader!css-loader?modules',
exclude: /node_modules/
}, {
test: /\.(png|woff|woff2|svg|ttf|eot)$/,
loader: 'url-loader',
exclude: /node_modules/
}]
}
}
I'd recommend using webpack to compile UI code for both client and server side in that case. Just set target: "node" in webpack config to produce bundle which can executed in Node environment.
That article might help for compiling your server side code with Webpack: http://jlongster.com/Backend-Apps-with-Webpack--Part-I
Especially on how to exclude node_modules with the externals key.
A very bare config might look like:
'use strict';
const path = require('path');
const fs = require('fs');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const rootDir = path.resolve(__dirname, '..');
const distDir = path.join(rootDir, 'dist');
const srcDir = path.join(rootDir, 'src');
const localStyles = new ExtractTextPlugin('local.css', { allChunks: true });
const nodeModules = fs.readdirSync('node_modules')
.filter(dir => !dir.startsWith('.'))
.reduce((acc, prop) => {
acc[prop] = 'commonjs ' + prop;
return acc;
}, {});
const loaders = [
{
test: /\.(js|jsx)$/,
include: srcDir,
exclude: /node_modules/,
loader: 'babel',
query: {
cacheDirectory: true,
},
},
{
test: /\.css$/,
include: srcDir,
loader: localStyles.extract(
'style',
'css?modules&localIdentName=[name]-[local]_[hash:base64:5]'
),
},
{
test: /\.json$/,
loader: 'json',
},
];
module.exports = {
target: 'node',
entry: {
server: ['server/index'],
},
output: {
path: distDir,
filename: '[name].bundle.js',
},
externals: nodeModules,
module: {
loaders,
},
plugins: [
localStyles,
],
};
Another solution (Webpack free) could be to use babel-plugin-css-modules-transform
.

Resources