Running scripts at build-time with Next.js - reactjs

I was wondering what the recommended way is to run a script that generates some static XML files at build time. Ideally, those scripts should be ES Modules so code can be shared between those scripts and my Next / React application code.
I think I need to customize the Webpack config but I'm not sure how I can run code there that uses ES Modules (and not just uses require).
EDIT: I have something like this in mind, which should also work with Modules:
{
webpack: (config, { isServer }) => {
if (isServer) {
require('./scripts/generate-xml');
}
return config;
}

It would be helpful if you could be more descriptive but from what I understand you need to run a particular script at Build-time for your NextJS app. You can make use of the getStaticProps() function provided by NextJS.
// your NextJS page
function Mypage() {
return (
<div></div>
)
}
// This is the function you need
export async function getStaticProps() {
// do anything you want to do at build-time
// A good way would be to call a function by importing and the function will be executed at runtime.
}
You can see more detail at official docs - https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
On another note, If you want to generate XML for SEO reasons at Build time, a better way would be by using API routes and feeding that API route as your XML file location inside robot.txt. See this - https://github.com/toughyear/blog/blob/master/pages/api/posts-sitemap.js

if you use a custom babel.config.js, you can use babel macros, but this will opt out from SWC in Nextjs 12. I am searching for an alternative to be compatible with SWC. For now the only workaround is to have a "pre-build" script.
You can wrap your npm run start script to run anything you want before calling next build

Related

Bundle javascript files in react with using next.js for loading page performance

I work with an SEO consultant for SEO optimizations and faster page loading. He said that too many individual JS is being called. This will delay the loading of the site. For this, it is the right alternative to make a single BUNDLE. i need to do it as 1 or 2 BUNDLE must OPTIMIZE according to which one gives better performance. my hierarchy and chucked js as follow after build ; I also searched about webpack but I could not adapt the codes I found, as follow the page of next js related to webpack. I added this and nothing changed.
module.exports = {
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//))
// Important: return the modified config
return config
},
}
I also use next js for server side rendering. Other picture shows my structure in the react project and i think i should refer config to index js. maybe. How i should forward to solve my problem ? If you need any other information, i will try to write it.

Create react app - how to copy pdf.worker.js file from pdfjs-dist/build to your project's output folder?

Since I can't use browser's pdf viewer in the network where the app is going to be used, I am testing a react-pdf package for loading PDF's with React.
I have made a component where I am sending a url of my PDF that I get from backend:
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
const PDFViewer = ({url}) => {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
function onLoadError(error) {
console.log(error);
}
function onSourceError(error) {
console.log(error);
}
return (
<div>
<Document
file={window.location.origin + url}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onLoadError}
onSourceError={onSourceError}
>
{[...Array(numPages).keys()].map((p) => (
<Page pageNumber={p + 1} />
))}
</Document>
</div>
);
};
export default PDFViewer;
But, on opening the PDFViewer I get an error
Error: Setting up fake worker failed: "Cannot read property 'WorkerMessageHandler' of undefined"
In documentation it says that you should set up service worker and that the recommended way is to do that with CDN:
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
But, I can't use CDN links for my project, and in the documentation it also says:
Create React App uses Webpack under the hood, but instructions for Webpack will not work. Standard instructions apply.
Standard (Browserify and others)
If you use Browserify or other bundling tools, you will have to make sure on your own that pdf.worker.js file from pdfjs-dist/build is copied to your project's output folder.
There are no instructions on how to do that with create-react-app. How can I set this up locally then?
Install pdfjs-dist
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
Reference: https://github.com/mozilla/pdf.js/issues/8305
found a more efficient way of including the worker
by including the library from the dependencies of react-pdf itself, this way you will never get a version mismatch like this The API version "2.3.45" does not match the Worker version "2.1.266"
if you install pdfjs-dist manually you will have to check react pdf dependency version on every build
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
see similar error on pdfjs library : https://github.com/mozilla/pdf.js/issues/10997
hope it helps people
You can install worker loader module for webpack:
npm install worker-loader --save-dev
Then use it inline where you are going to work with a worker:
import SomeWorker from 'worker-loader?inline=true!../workers/some.worker'
const someWorker: Worker = new SomeWorker()
someWorker.postMessage(...)
I haven't tried this solution with react-pdf, but it might help.
You may need to add types for TypeScript if you are using it:
declare module 'worker-loader*' {
class SomeWorker extends Worker {
constructor()
}
export default SomeWorker
}
Just to add that in some .d.ts file in your project.
Install pdfjs-dist then use the webpack module:
import { pdfjs } from 'react-pdf'
import worker from 'pdfjs-dist/webpack'
pdfjs.GlobalWorkerOptions.workerSrc = worker
If your build process uses cli commands, (i.e. AWS buildspec), you can use this:
mkdir -p build && cp ./node_modules/pdfjs-dist/build/pdf.worker.js build
If you are in a corporate codebase environment and have little to no experience configuring WebPack, I wanted to share a little more info if (like me) you struggled with this for quite a long time.
My environment has several complicated WebPack config files (base, production, and development), and the resolution ended up being pretty simple but it escaped me for quite a while because I was unfamiliar with the complicated build process.
1) The Implementation
Quite simple, just as the docs recommend (I went with the minified file). Our React environment required me to use React-PDF#4.2.0, but there aren't any differences here.
import {Document, Page, pdfjs} from 'react-pdf'
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.min.js'
Note: a previous solution recommended grabbing the source from the react-pdf node_modules folder, however, my codebase is setup to install dependencies separately somehow because when I npm install react-pdf, pdfjs-dist is also installed separately. Regardless, this method did not work for my codebase (importing the worker as a variable) due to the way the project is built. The import command acted like it couldn't find the proper named export inside a node_modules folder. It was top-level or nothing.
2) WebPack Config
Since I do not know WebPack at all, but found pretty easily that what I needed to do was take advantage of CopyWebpackPlugin, I searched through those existing dev and prod webpack config files, and found existing copy commands for JQuery and polyfill and added a new plugin to that array:
new CopyWebpackPlugin({from: 'node_modules/pdfjs-dist/build/pdf.worker.min.js})
I had to do this in multiples places in both config files as this large project has several entry point server files for the different services of the website.
3) Inserting Script Tag to HTML Head
This was the crucial part I was missing. There was a "ComponentFactory" file whose job it was to insert chunks of html in the <head> and tail of the html file. I wasn't used to something like this on small projects. So there, I simply copied what was already done for the jquery and polyfill, which included a string literal of the location of the assets folder the webpack was building out to. In my case, that was something like "assets/v1/". So the tag looked like this:
<script src=`${STATIC_ASSETS_URL}/pdf.worker.min.js` defer></script>
It works perfectly, however I am still getting the "Setting Up a Fake Worker" but immediately after that, it loaded it successfully in console and checking the dev tools, it was using the proper file. It's probably just a timing thing of the src set not running high enough in the code, but it was not effecting the outcome, so I let it go.
(Sidebar, if you also get the "TT unknown function" (paraphrasing) error, that can be ignored. It's just a font issue with whatever PDF you are loading and is just a warning, not an error.)
I was facing this issue once I had to use "react-pdf" from within a package.
It was solved by importing the worker conditionally into the code:
Conditional import:
export const getWorker = () => {
try {
return require('react-pdf/node_modules/pdfjs-dist/legacy/build/pdf.worker.entry.js')
} catch () {
return require('pdfjs-dist/legacy/build/pdf.worker.entry.js')
}
}
usage:
import { Document, Page, pdfjs } from 'react-pdf/dist/umd/entry.webpack'
pdfjs.GlobalWorkerOptions.workerSrc = getWorker()

Where should I put general app config in a Gatsby project?

I'm new to Gatsby, but familiar with CRA. I'm using the default Gatsby setup generated by running gatsby new <project_name> from Gatsby CLI.
I have some general config that I want run in my whole project, regardless of current page - for example:
import { enableMapSet, enableES5 } from "immer";
enableMapSet();
enableES5();
In CRA projects I put this stuff inside App.tsx, but can't figure out what the right place is in a Gatsby project.
In Gatsby, you don't have a single configuration file per se. There are several APIs exposed by Gatsby that allows you to configure one specific part of your site.
To run a set of functions regardless of the page you can use a gatsby-browser.js (placed in the root of the project).
Basically, Gatsby exposes a bunch of APIs that lets you respond to actions within the browser, and wrap your site in additional components. The Gatsby Browser API gives you many options for interacting with the client-side of Gatsby. One of them, onClientEntry fits your requirements. From its documentation:
onClientEntry
(_: emptyArg, pluginOptions: pluginOptions) => undefined
Called when the Gatsby browser runtime first starts.
Applied to your code, your gatsby-browser.js should look like:
import { enableMapSet, enableES5 } from "immer";
import React from 'react';
export const onClientEntry = () =>{
enableMapSet();
enableES5();
};
The snippet above will trigger enableMapSet() and enableES5() on every page.

requestAnimationFrame is not defined it Next.js with React Native Web (Animated module)

I'm working on Next.js and React-Native-Web. I managed to run them together following the official Next.js example but when I'm trying to use the Animated package from the react-native it fails with Error that the requestAnimationFrame isn't defined. Basically this functionality does the node_modules package but I set the alias in webpack to translate all react-native requires to the react-native-web so even the node_modules package should use the react-native-web.
Any suggestions on how to solve it?
ReferenceError: requestAnimationFrame is not defined
at start (...node_modules\react-native-web\
dist\cjs\vendor\react-native\Animated\animations\TimingAnimation.js:104:11)
enter code here
Thanks for any help!
The problem is in the missed RequestAnimationFrame functionality at the server. This error happens when Next.js tries to render the component during SSR.
Unfortunately, there is no polyfill, etc. for such purpose so I just decided to use the Next.js dynamic imports for a Component that has animation functionality.
Next.js Official documentation
My own case оust to show how code looks:
import dynamic from 'next/dynamic';
const AutocompleteDropdown = dynamic(
() => import(
'myAwesomeLib/components/dropdown/autocomplete/AutocompleteDropdown'
),
{
ssr: false,
}
);
Now you can use the AutocompleteDropdown as the standard JSX component
I'm coding an App with React Native Web and NextJS 12, and in 2021 I encounter this problem and I fixed it, but now I know my fix was only for Next Dev, because it returned for Next Production Build.
Solution details:
No Dynamic import (which is useful too, but can be annoying when having lot of components using it)
Using RAF polyfill and Webpack ProvidePlugin.
Main thing to have in mind is that next.config.js with webpack 5 is going to check the codes first before even reach next entry points _documents.js and _app.js. It means that, you can put polyfill in those entry point files, it will still raise error of RAF undefined. You have to make requestAnimationFrame ready for config check.
DEV approach that will work on Next DEV only. Install RAF package https://www.npmjs.com/package/raf and In next.config.js add codes:
const raf = require('raf');
raf.polyfill();
This will add requestAnimationFrame and cancelAnimationFrame function to global and window object if they don't have it. In our case, it would add it in global for NodeJS.
But this solution won't work when executing npm run dev. I don't know why, if anyone knows why Next or Webpack 5 act differently from DEV to PRODUCTION, let me know.
Complete Solution:
Use ProvidePlugin config of webpack 5 https://webpack.js.org/plugins/provide-plugin/ . Create a file to use as modules, let's say: raf.js in root project or anywhere you want:
const raf = require('raf');
const polys = {};
raf.polyfill(polys);
module.exports = polys.requestAnimationFrame;
And in next.config.js use it inside webpack: () = {} like:
webpack: (config, options) => {
// console.log('fallback', config.resolve.fallback);
if (options.isServer) {
// provide plugin
config.plugins.push(
new options.webpack.ProvidePlugin({
requestAnimationFrame: path.resolve(__dirname, './raf.js'),
}),
);
}
And now, it's up to you to adapt to your existing config logic. By doing this, in Production Build, NextJS is injecting the requestAnimationFrame function in Server Side everywhere a module is using it.

Build once and deploy build file in multiple environments with minimal changes React and Webpack

In webpack.config file declared a variable to read in application.
let BASEURL = "http://127.0.0.1:8090";
with this approach I am not able to update BASEURL after npm run build. Every time I want to generate a new build for each environment if BASEURL changes.
Is there any way to build once and deploy build file in multiple environments with minimal changes?
Tl;dr: use AJAX and have the config either in react context or in a global variable.
Detailed answer:
It is indeed as you state, after the application is built with npm run build, the environment variables become hardwired and cannot be changed.
The official statement of create-react-app is it does not support the build once deploy many principle. From https://create-react-app.dev/docs/adding-custom-environment-variables/ :
The environment variables are embedded during the build time. Since Create React App produces a static HTML/CSS/JS bundle, it can’t possibly read them at runtime.
However, there are ways of achieving the principle, just a bit more complicated. The idea is, you need to get the value of a variable at runtime from an external source, e.g. AJAX. In more details, possible solutions may be (but are not limited to) following:
1. server-side placeholder replacement
This is a solution proposed by create-react-app in https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page, introducing a custom placeholder and replacing it with the data on the server, before it is rendered to clients.
<!doctype html>
<html lang="en">
<head>
<script>
window.SERVER_DATA = __SERVER_DATA__;
</script>
While this works, it introduces a major overhead, because it leaves the whole backend implementation up to you. Depending on your tech stack, this may be very easy or also very complicated to implement.
2. a dynamic <script> that assigns variable values
A solution proposed in https://www.cotyhamilton.com/build-once-deploy-anywhere-for-react-applications/ utilizes the dynamic nature of javascript. In the dynamically downloaded config.js file, a value is assigned to the variable. In the rest of the React code, the variable is read and used. You can change the config.js file any time, without the need of recompiling the react app.
// public/config.js
const apiUrl = 'localhost:1337';
const env = 'development';
<!-- public/index.html -->
<script src="%PUBLIC_URL%/config.js"></script>
<script>
window.config = { apiUrl, env };
</script>
The main downside is that this does not support TypeScript, and your IDE or linter may complain that apiUrl and env are not defined. Especially in bigger projects, this approach may be hard to maintain.
3. dynamic config with AJAX, with TypeScript support
Based on the 2nd solution, this article https://profinit.eu/en/blog/build-once-deploy-many-in-react-dynamic-configuration-properties/ describes in a great detail how to best achieve the build once deploy many principle with create-react-app and what are pros and cons.
It proposes downloading the dynamic config as a JSON with AJAX. The main caveat is to make sure that the dynamic config is downloaded BEFORE some code tires to use it. In the context of React lifecycle, there are two ways of how to achieve this.
3.1 global variable
Download the dynamic config JSON from globalConfigUrl, store it in a global variable, and only then render the React app. Example in TypeScript:
// index.tsx:
import axios from "axios";
import React, {ReactElement} from "react";
import App from "./App";
import {globalConfig, globalConfigUrl} from "./configuration/config";
axios.get(globalConfigUrl)
.then((response) => {
globalConfig.config = response.data; // THIS IS THE IMPORTANT LINE
return <App />;
})
.catch(e => {
return <p style={{color: "red", textAlign: "center"}}>Error while fetching global config</p>;
})
.then((reactElement: ReactElement) => {
ReactDOM.render(
reactElement,
document.getElementById("root")
);
});
Full working example: https://codesandbox.io/s/build-once-deploy-many-global-config-object-dvpzr
3.2. React context
Wrap your <App> component with a react context provider containing the configuration (with undefined or some default value). Fetch the configuration first time App is rendered and then save its value to the context. React will take care of the rest and will propagate the value change!
Full working example: https://codesandbox.io/s/build-once-deploy-many-react-context-7lk7g
The basic idea is this. Check the article above / working example for all details:
// App.tsx
import {useConfig} from "./configuration/useConfig";
// ... in the method:
const { setConfig } = useConfig(); // the `useConfig` is a custom hook, wrapping a React context. See the full working example for all details
useEffect(() => {
axios
.get(dynamicConfigUrl)
.then((response) => {
setConfig(response.data);
})
}, [setConfig]);

Resources