Can't resolve 'fs' in nextjs with custom express server - node.js

I add fs to my nextjs project and received the following error:
Module not found: Can't resolve 'fs' in '/Users/neven/Development/next-js/node_modules/dotenv/lib'
I found that to resolve this issue, I should add config.node = { fs: 'empty' } to my next.config.js file.
The problem is that when I add that config param, dotenv plugin stops working, that is env variables are not loaded on client side.
This is my next.config.js file, which works without any issues.
const withCSS = require('#zeit/next-css')
const dotenv = require('dotenv')
const path = require('path')
const Dotenv = require('dotenv-webpack')
dotenv.config()
module.exports = withCSS({
webpack: config => {
config.plugins = config.plugins || []
config.plugins = [
...config.plugins,
// Read the .env file
new Dotenv({
path: path.join(__dirname, '.env'),
systemvars: true,
}),
]
return config
},
})
And then when I add fs: 'empty', it looks like this:
const withCSS = require('#zeit/next-css')
const dotenv = require('dotenv')
const path = require('path')
const Dotenv = require('dotenv-webpack')
dotenv.config()
module.exports = withCSS({
webpack: config => {
config.plugins = config.plugins || []
config.node = {
fs: 'empty'
}
config.plugins = [
...config.plugins,
// Read the .env file
new Dotenv({
path: path.join(__dirname, '.env'),
systemvars: true,
}),
]
return config
},
})
Do you have any suggestions on how I could work this thing out?
Let me know in case additional details are needed.

I found out what the issue was; dotenv plugin is working correctly, but I was trying to get the variables on client side, and that is not possible in this way.
The solution to use env variables on client side is to add env: { EXAMPLE: 'helloWorld' } to next.config.js file.
const withCSS = require('#zeit/next-css')
const dotenv = require('dotenv')
const path = require('path')
const Dotenv = require('dotenv-webpack')
dotenv.config()
module.exports = withCSS({
env: { EXAMPLE: 'helloWorld' },
webpack: config => {
config.plugins = config.plugins || []
config.node = {
fs: 'empty'
}
config.plugins = [
...config.plugins,
// Read the .env file
new Dotenv({
path: path.join(__dirname, '.env'),
systemvars: true,
}),
]
return config
},
})

The issue here is that your client-side can't access the environment variables.
Started NextJS 9.4, you can use .env* files to add your environment variables.
For your client-side to get access access to the environment variables, you just need to prefix them with NEXT_PUBLIC_
NEXT_PUBLIC_YOUR_KEY="keykeykey"
These can be accessible with:
process.env.NEXT_PUBLIC_YOUR_KEY

Related

NextJS Webworker `window is not defined`

Getting a window not defined error when using NextJS and next-web-worker package.
require('dotenv').config();
const withPlugins = require('next-compose-plugins');
const withCSS = require('#zeit/next-css');
const withWorkers = require('#zeit/next-workers');
const Dotenv = require('dotenv-webpack');
const webpackConfig = (config) => {
config.plugins = config.plugins || [];
config.target = 'universal';
config.output = {
...config.output,
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
globalObject: '(this || global)',
publicPath: 'http://localhost:3000'
};
config.plugins = [
...config.plugins,
// Read the .env file
new Dotenv({
path: path.join(__dirname, '.env'),
systemvars: true
})
];
return config;
};
module.exports = withPlugins([[withCSS], [withWorkers]], webpackConfig);
The recommended work around appears to be setting the globalObject to this or global But that isn't working. Are there any other work arounds for this?
I was just experiencing this same error and it was solved by following the advice here:
yarn add #zeit/next-workers#canary

Dynamic require inside a webpacked express server can not find module

I have a really small express application. Basically a poor man's server less function provider. However this is nothing I use in production and it works in an unpackaged mode. But after I webpack this application the dynamic require always fails with an Error: Cannot find module exception. But the module is there and the path is correct.
devserver.js:
const express = require('express')
const fs = require('fs')
const resourceMapper = fs.existsSync(__dirname + '/../webpack.config.js') ? require('../webpack.config') : null
const app = express()
// enable express json
app.use(express.json())
// add dynamic lambda functions
app.post('/api/:lambda', function(request, response) {
const resource = resourceMapper
? `${__dirname}/.${resourceMapper[1].entry[request.params.lambda.replace(/^common-/, "").replace(/\.js$/, "")]}`
: `${__dirname}/${request.params.lambda}`
const lambda = require(resource) // <--------- problematic require
const result = lambda.main(request.body)
response.status(200).json(result)
});
app.listen(3000)
webpack.conf.js:
module.exports = [
{
name: "development-server",
target: 'node',
externals: [(require('webpack-node-externals'))()],
node: {
__dirname: false,
__filename: false,
},
entry: {
devserver: "./devserver/server.js",
},
output: {
filename: "./server.js",
path: __dirname + '/build/node/'
}
},
{
name: "lambdas",
target: 'node',
externals: [(require('webpack-node-externals'))()],
entry: {
returns: "./lambdas/returns/src/main/index.js",
echo: "./lambdas/echo/src/main/echo.js"
},
output: {
library: "[name]",
libraryTarget: "commonjs2",
filename: "./common-[name].js",
path: __dirname + '/build/node/'
}
}
]
I have really tried lots of things and various modules and plugins. In the end they only way I got it working is using a very nasty eval trick.
const require2 = eval('require')
//[snip]
const lambda = resourceMapper
? require(`${__dirname}/.${resourceMapper[1].entry[request.params.lambda.replace(/^common-/, "").replace(/\.js$/, "")]}`)
: require2(`./${request.params.lambda}`)

Set initial state of store in an isomorphic react-redux app from NodeJS server (I am using react-router)

I am trying to set the initialState of my redux store from the NodeJS server. I am unsure on what I missed and how to get it to work.
Here's my server.js
const express = require('express');
const path = require('path');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackHotServerMiddleware = require('webpack-hot-server-middleware');
const config = require('./webpack/webpack.development.config.js');
const compiler = webpack(config);
const app = express();
app.use(webpackHotMiddleware(compiler.compilers.find(
compiler => compiler.name === 'client'
)));
app.use(webpackHotServerMiddleware(compiler));
app.listen(3000);
Here's my webpack.development.config
const path = require('path');
module.exports = [
{
name: 'client',
target: 'web',
entry: './routes/client.jsx',
output: {
path: path.join(__dirname, 'assets'),
filename: 'client.js',
publicPath: '/assets/',
},
resolve: {
extensions: ['.js', '.jsx']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]__[local]___[hash:base64:5]',
sourceMap: true
}
},
{
loader: 'sass-loader'
}
]
},
{ test: /\.html$/, loader: "html-loader" }
],
},
},
];
PS, the same modules are there for server, but I've removed in an attempt to keep it short.
Here's the client.jsx
import React from 'react';
import { createStore } from 'redux'
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Routes from './routes.jsx';
import { Provider } from 'react-redux';
import MainStore from '../views/store/MainStore';
import home from './home';
const store = createStore(MainStore, "Name");
ReactDOM.render((
<Provider store={store}>
<BrowserRouter>
<Routes />
</BrowserRouter>
</Provider>
), document.getElementById('root'));
And finally my home.jsx
const express = require('express');
const path = require('path');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackHotServerMiddleware = require('webpack-hot-server-middleware');
const config = require('../webpack/webpack.development.config.js');
const router = express();
router.get('/test', (req, res) => {
res.json({ message: "I'm just testing to see if this works" });
});
const name = "Mona";
module.exports = {
router: router,
name: name,
};
I would like to import const name from home.jsx to client.jsx and set the initial value of the store as this imported value. I could do it manually by the line
const store = createStore(MainStore, "Name");
in client.jsx. But instead of using "Name" as a string, I would like to replace it with the constant value in home.jsx.
Any suggestions?
Set initial state of store in an isomorphic react-redux app
Typical solution for supposed task is using preloaded state, which had been calculated at SSR phase and injected as serialized informational field in rendered HTML-template. Also you can create dedicated resource, like /api/initialState.js, which create initial state for your view model, dependent on client-side request and maybe JWT credentials.
You can see similar solution in resolve-scripts npm package, which provides ability for full-automatic isomorphic storage for redux on server, including SSR.

Webpack-dev-server doesn't find files to serve

I just copy-pasted webpack-dev-server + express settings from webpack docs, but it doesn't work because the server can't find files to serve. What is wrong with this setup?
server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('../webpack.config.js');
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));
/*app.use('/',express.static('public')) it works when used insted of webpack dev middleware */
// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const scssRules = {
test:/\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
};
const jsRules = {
test: /\.js$/,
use: [
{loader: 'babel-loader'}
]
};
module.exports = {
entry: './js/App.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public'),
publicPath: '/'
},
module: {
rules: [
jsRules,
scssRules
]
},
devServer: {
contentBase: './public'
},
plugins:[
new webpack.HotModuleReplacementPlugin()
],
devtool: 'inline-source-map'
}
File structure:
What i see:
I also came across this problem.
Turns out webpack-dev-middleware doesn't output any files, instead serves from memory. While the express server is expecting physical files.
Here, this plugin might help: https://github.com/gajus/write-file-webpack-plugin

How to pass process.env.NODE_ENV from Gulp to Webpack?

I have a gulpfile, where I create a webpackDevServer to live-update my js code. I set a process.env.NODE_ENV variable in gulpfile, but for some reason webpack doesn't see it - it is undefined.
Here's the relevant piece of my gulpfile.js:
gulp.task("watch", ["_set-env:dev"], function() {
// modify default webpack configuration for Development Server
var webpackDevConfig = Object.create(webpackConfig);
webpackDevConfig.devtool = "eval";
webpackDevConfig.debug = "true";
new webpackDevServer(webpack(webpackDevConfig), {
proxy: {
"/api/*": {target: "http://localhost:8000", secure: false},
"/static/*": {target: "http://localhost:8000", secure: false},
"/media/*": {target: "http://localhost:8000", secure: false}
}
}).listen(8001, "localhost", function (err) {
if (err) throw new gutil.PluginError("webpack-dev-server", err);
gutil.log("[webpack-dev-server]", "http://localhost:8001" + webpackDevConfig.output.publicPath);
});
});
gulp.task("_set-env:dev", function() {
gutil.log("set-env", "ENV => development");
genv({
vars: {
NODE_ENV: "development"
}
});
});
Then in webpack I check its value and it is undefined:
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
...
const environmentsFile = path.join(__dirname, "/environments.json");
const nodeModulesPath = path.join(__dirname, "/node_modules");
const bowerComponentsPath = path.join(__dirname, "/bower_components");
console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
console.log(process.env.NODE_ENV);
const webpackConfig = {
entry: {
app: ["app.js"]
},
And on the console I see:
$ gulp watch
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
undefined
[22:28:34] Using gulpfile ~/Documents/frontend/gulpfile.js
[22:28:34] Starting '_set-env:dev'...
[22:28:34] set-env ENV => development
[22:28:34] Finished '_set-env:dev' after 7.63 ms
[22:28:34] Starting 'watch'...
You probably have something like this close to the top of your gulpfile:
var webpackConfig = require('./webpack.config.js');
That means your webpack configuration is evaluated before the _set-env:dev task even runs. Remember: your gulpfile only defines tasks. The tasks themselves aren't run until after the entire gulpfile has been evaluated.
You need to defer requiring your webpack configuration until after the _set-env:dev task has run by deleting the line at the top of your gulpfile and putting the require() directly into your watch task:
gulp.task("watch", ["_set-env:dev"], function() {
// modify default webpack configuration for Development Server
var webpackDevConfig = Object.create(require('./webpack.config.js'));

Resources