Search code examples
javascriptnode.jsreactjsexpressmern

(MERN) What is the correct way to serve a React App using Express.js?


So basically my issue is that I've mostly only made APIs using node/express/mongoose and have never served my react apps since someone else always did that for me in our Plesk (I think through Apache.)

Now that I'm finally doing some MERN stack stuff for a client on the side I was wondering what the 'correct' way of serving a React App that has already been built.

Express and React-Router clash. BY that I mean that if I have an 'About' page and I go to example.com/about how does express know that's a page on my single-page React app? There's only a index.html anything else dealing with routes on my page are handled by react-router.

But what if it is a file or a folder? So this has been my solution for being able to serve both the website's files and still being able to access the website's routes:

const apps = { //websites are put in this object and iterated through to make the code below less bloated.
        example: {
            folder: "example-website",
            app: express(),
            server: null,
            port: 80,
            protocol: "http", 
            cert: null,
            subdomains: []
        }
    }
    
    for(let website of Object.keys(apps)){ //Iterate over every website.
        website = apps[website]; //Make the website key the website object instead.
    
        const websitePath = path.join(__dirname+`/client/${website.folder}/build`); //Save path to website's build folder for ease of use.
    
        fs.readdir(websitePath, (err, files) => { //iterate over all files in the build folder
            if(err){ //log errors.
                console.log(err)
            } else { //continue if there wasn't an error.
                const fileArray = files.filter(file => file !== 'index.html')//remove 'index.html' from the array.
    
                website.app.use('/*', (req,res) =>{
                    let route = req.params["0"].substring(0, req.params["0"].indexOf('/')); //take the first part of the route.
    
                    if(fileArray.includes(route)){ //check if the first part of the route matches a file or folder.
                        res.sendFile(path.join(__dirname+`/client/${website.folder}/build/${req.params["0"]}`));
                    } else { //If the list of files doesn't include the route than the route is probably a route within the React App in React-Router
                        res.sendFile(path.join(__dirname+`/client/${website.folder}/build/index.html`));
                    }
                    
                });
            
                //...more irrelevant code here.
            }
    
        })
    }

I was wondering if this was the correct way to deal with the conflict between my single-page React app's use of react-router and the way Express handles routes.


Solution

  • I am not really sure that maybe you were confused about the API, I will explain that out maybe you can better understand about MERN,

    First there is 2 kind : 1 is backend(API) 2 is Frontend (in this case is Reactjs), so backend and front end, each has its own business, i am sure you knew that , so Once the UI(Frontend in this case is Reactjs ) need data they will call api by fetch(basicly) call to the backend(API in this case is Nodejs) and get that data out .

    => so you know how Frontend and backend work , next :

    Reactjs they divide many element to a component to load

    • Sample : google.com
    • See : in the top right there is a header it is a component , a input is another component and the other is another component.

    • It run : when you search something, the header and the input is not load , the other component is load , so when url change , the react-router will help you catch url you define in router you can read more here about react-router .

    ==> It mean , when go to different url , they only trigger the component that url use localhost:3000/ to localhost:3000/about