reactjswebpackreact-routercreate-react-appwebpack-production

React App with basename not working in production build, but works in development (create-react-app)


I have created an app using create-react-app with a basename '/admin' and it is working just fine in development mode. All routes working properly both on localhost and behind nginx proxy.

When I build the app using npm run build I get a blank screen on the '/admin' url, with the following errors in the console:

The script from “https://192.168.1.2/admin/static/js/main.49bb4878.js” was loaded even though its MIME type (“text/html”) is not a valid JavaScript MIME type.

The stylesheet https://192.168.1.2/admin/static/css/main.4efb37a3.css was not loaded because its MIME type, “text/html”, is not “text/css”.

Uncaught SyntaxError: expected expression, got '<' main.49bb4878.js:1

I have tried both <BrowserRouter pathname="/admin">...</BrowserRouter> and the one I have in the following index.js file.

It seems like the server sends the index.html file no matter what the client requests...

This is my index.js file:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import storeToConfigure from './configureStore';
import CustomRouter from './utils/CustomRouter';
import * as buffer from 'buffer';
import { BrowserRouter } from 'react-router-dom';
console.log(window);
window.Buffer = buffer;
window.process = {}

export const store = configureStore(storeToConfigure);

export const history = createBrowserHistory({ basename: '/admin' });

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
    <Provider store={store}>
        <CustomRouter history={history} basename="/thug-app">
            <ScrollToTop>
                <App />
            </ScrollToTop>
        </CustomRouter>
    </Provider>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint.
reportWebVitals(console.log);

This is the CustomRouter I'm using, in order to be able to directly access the history anywhere without a hook:

import React, { useLayoutEffect, useState } from 'react';
import { Router } from 'react-router-dom';

const CustomRouter = ({
    basename,
    children,
    history,
}) => {
    const [state, setState] = useState({
        action: history.action,
        location: history.location,
    });

    useLayoutEffect(() => history.listen(setState), [history]);

    return (
        <Router
            basename={basename}
            children={children}
            location={state.location}
            navigationType={state.action}
            navigator={history}
        />
    );
};

export default CustomRouter;

Again, everything works just fine in development. The problem is when I build the app for production. I have tried both pm2 and serve packages (same names on npmjs). The serve package returns 404, while pm2 returns the errors I mention above.

Thank you for taking the time to help!


Solution

  • I managed to make it work with this workaround:

    I ditched both pm2 and serve and used a custom express.js server.

    The setup:

    const express = require('express');
    const path = require('path');
    const fs = require('fs');
    
    const app = express();
    
    // app.use(express.static(path.join(__dirname, 'build')));
    
    app.get('/*', (req, res) => {
        
        let theUrl = req.originalUrl.replace('/admin', '');
        if (!!!theUrl) {
            theUrl = 'index.html';
        }
    
        try {
            const target = path.join(__dirname, 'build', theUrl);
            console.log(target);
            if (fs.statSync(target)) {
                res.sendFile(target);
            } else {
                res.sendFile(path.join(__dirname, 'build', 'index.html'));
            }
        } catch (error) {
            console.log(error);
            res.sendFile(path.join(__dirname, 'build', 'index.html'));
        }
    });
    
    app.listen(4000, () => console.log('admin app listening on 4000'));
    

    It also seems to work fine without the following line:

    app.use(express.static(path.join(__dirname, 'build')));
    

    Like I had noticed, both pm2 and serve sent the index.html file no matter what the client requested.

    Seems to be working fine behind nginx proxy as well.

    Whenever the requested file does not exist, simply serve the index.js file.