Search code examples
reactjsexpressserverlessmernvercel

Hosting a MERN application with Vercel in 2021 (without Next.js)


I've been using Vercel to host several React web applications and it's been great! Recently, I upgraded an existing project with a backend (Express.js and MongoDB Atlas) and I'd like to host it there as well.

On my machine, the MongoDB server and all the CRUD routes are running smoothly on localhost:5000, but I'd like to know how to add this to the Vercel deployment. Vercel's documentation focuses on serverless deployment, and while I don't mind rewriting my backend if I have to, I'm wondering if there's an easier solution for adding a server/database.

The MERN stack is still relatively popular so I imagine a lot of developers have found a way to host this kind of project. If anyone can help, I would really appreciate it!


Solution

  • according to your comment your file structure should be look like this

    // + refer as close folder
    // - refer as open folder
    // > refer as file
    
    main
    
      - backend
        + models
        + routes
        > package.json
        > package-lock.json
        > server.js
    
      - frontend
        + public
        - src
          + pages
          + components
        > package.json
        > package-lock.json
    
    

    step1 - edit frontend package.json
    add homepage

    {
      // initially add link whatever you suppose to be link of your site
      // then after deploying to vercel, you get the exact link
      // then replace it with vercel link in "homepage" key
    
      "homepage": "https://awesome-app.vercel.app",
      "name": "awesome-app",
      "version": "1.0.0,
      "private": true,
      ...rest of your frontend package.json file
    }
    

    step2 - add basename

    // you wrapped your app with either BrowserRouter or HashRouter
    // add basename prop so it refer to your package.json homepage
    // if your route are '/awesome-route' then is converted to
    // https://awesome-app.vercel.app/awesome-route
    
    <BrowserRouter basename='/'>    // or HashRouter
      <Switch>
        ...your routes
      </Switch>
    </BrowserRouter>
    

    step3 - build your react

    you have to build your frontend everytime before deployment if and only if your change your frontend code

    then your frontend should look like below

      - frontend
        + build    // build folder at root of your frontend
        + public
        - src
          + pages
          + components
        > package.json
        > package-lock.json
    
    

    step4 - change your server.js file
    should look like this

    // i use "dotenv" package
    // in your case must be located at "main > backend > .env"
    // see the final file structure at bottom if you don't understand
    
    if (process.env.NODE_ENV !== 'production') {
      require('dotenv').config({path: __dirname+'/.env'});
    }
    
    const express = require('express');
    const mongoose = require('mongoose');
    const path = require('path');
    
    const app = express();
    app.use(express.json());
    
    const port = process.env.PORT || 5000;
    
    mongoose.connect(process.env.mongoURI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
      useFindAndModify: false,
    
      // remove poolSize or set according to your need
      // read docs before setting poolSize
      // default to 5
      poolSize: 1
    })
      .then(() => {
        app.listen(port);
      })
    
    // all your routes should go here
    app.use('/some-route', require(path.join(__dirname, 'api', 'routes', 'route.js'));
    
    // static files (build of your frontend)
    if (process.env.NODE_ENV === 'production') {
      app.use(express.static(path.join(__dirname, '../frontend', 'build')));
      app.get('/*', (req, res) => {
        res.sendFile(path.join(__dirname, '../frontend', 'build', 'index.html'));
      })
    }
    

    step5 - add vercel.json at root level of your repository i.e., in your case main directory

    {
      "version": 2,
      "builds": [
        {
          "src": "./backend/server.js",  // path to your server.js file
          "use": "@vercel/node"
        },
        {
          "src": "./frontend/build",    // path to your build folder
          "use": "@vercel/static"
        }
      ],
    
      // rewrites any request to api call with server.js
      // now your "app.use('/some-route')" would work as normal as localhost
      // no need to change your codes to serverless way
    
      // also remember here "/(.*)" is not regular js regex
      // it follows path-to-regex
      // playground link: https://regexr.com
    
      "rewrites": [
        {
          "source": "/(.*)",
          "destination": "/backend/server.js"
        }
      ]
    }
    

    finally your file structure look like this

    // + refer as close folder
    // - refer as open folder
    // > refer as file
    
    main
    
      - backend
        + models
        - routes
          > route.js
        > package.json
        > package-lock.json
        > .env
        > server.js
    
      - frontend
        - build
          + static
          > manifest.json
          > index.html
        + public
        + src
        > package.json
        > package-lock.json
    
      > vercel.json   // in your main directory's root
    
    

    the disadvantage of this method is that your have to build your frontend every time you push your repo to github

    remember to build your frontend before you push your repo to github if and only if you change your frontend code

    also remember to replace your homepage url in your frontend package.json file after deployment with vercel provided url or your custom domain url

    Happy Coding :)