Ravindra BagaleCourses & study guides

Chapter 12: Technology Stacks — LAMP, LEMP, MEAN, MERN

12.7 MERN: React frontend served by Nginx

Chala, aata frontend build karuya. Bagha how the React code becomes simple static files that Nginx can serve.

Build the React app (Vite)

cd /opt
sudo mkdir -p /opt/mern-frontend && sudo chown $USER:$USER /opt/mern-frontend
cd /opt/mern-frontend
npm create vite@latest . -- --template react
npm install

(If create vite asks any questions, accept the defaults and do not start the dev server.)

Replace src/App.jsx with a small notes UI that talks to /api:

cat > src/App.jsx <<'EOF'
import { useEffect, useState } from "react";

export default function App() {
  const [notes, setNotes] = useState([]);
  const [text, setText] = useState("");

  const load = () => fetch("/api/notes").then((r) => r.json()).then(setNotes);
  useEffect(() => { load(); }, []);

  const add = async (e) => {
    e.preventDefault();
    if (!text.trim()) return;
    await fetch("/api/notes", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text }),
    });
    setText("");
    load();
  };

  return (
    <div style={{ fontFamily: "Arial", maxWidth: 600, margin: "40px auto" }}>
      <h1>MERN Notes on AWS EC2</h1>
      <form onSubmit={add}>
        <input value={text} onChange={(e) => setText(e.target.value)} placeholder="Write a note" />
        <button type="submit">Add</button>
      </form>
      <ul>{notes.map((n) => <li key={n._id}>{n.text}</li>)}</ul>
    </div>
  );
}
EOF

npm run build            # creates the dist/ folder
sudo mkdir -p /var/www/mern
sudo cp -r dist/* /var/www/mern/
sudo restorecon -Rv /var/www/mern 2>/dev/null     # CentOS only

(With Create React App the output folder is build/ instead of dist/.)

Nginx configuration for MERN

# AL2023 / CentOS: /etc/nginx/conf.d/mern.conf
# Ubuntu: save as /etc/nginx/sites-available/mern, symlink into sites-enabled, remove 'default',
#         and use "listen 80 default_server;" + "server_name _;"
sudo tee /etc/nginx/conf.d/mern.conf > /dev/null <<'EOF'
server {
    listen 80;
    server_name YOUR_SERVER_NAME;
    root /var/www/mern;
    index index.html;

    # Single Page App: unknown paths go to index.html (client-side routing)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API requests go to the Node/Express backend
    location /api/ {
        proxy_pass http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # Cache hashed static assets
    location /assets/ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}
EOF
sudo sed -i "s/YOUR_SERVER_NAME/$(curl -s https://checkip.amazonaws.com)/" /etc/nginx/conf.d/mern.conf
sudo setsebool -P httpd_can_network_connect 1 2>/dev/null     # CentOS only
sudo nginx -t && sudo service nginx reload

Open http://<PUBLIC_IP>/, add notes, refresh — they persist in MongoDB.

Ravindra Bagale's Tip

On a small t3.micro, build your React/Angular app on your laptop (npm run build) and upload only the dist/ folder with scp. Builds need a lot of RAM and can freeze a 1 GiB instance. The server should only serve files, not build them — this is also how CI/CD pipelines work in companies.