Ipswitch
← All writing

Fitting a Next.js app into a 500 MB cPanel quota

Build off the server, ship only what runs, and keep the data where a redeploy cannot touch it.

The hosting account is a Safaricom Cloud cPanel Bronze package: 1 GB of RAM, one CPU core, a 500 MB disk and 20 entry processes. Node 24 and Python 3.11 are available through cPanel’s app selectors. The database is SQLite, because the plan has no PostgreSQL and the filesystem is persistent.

The 500 MB is the binding constraint, and it is shared by the app, the database and every uploaded image. A normal npm install on the server would not fit. This project’s node_modules alone exceeds the quota.

Build somewhere else

So the frontend is built off the server, on my machine, into Next’s standalone output, which bundles only the modules the app actually uses. One command produces both bundles:

SITE_URL=https://chamachamiminawewe.co.ke ./scripts/build-cpanel.sh

The script runs the Next build, then assembles what the server needs and nothing else:

The numbers

The frontend bundle is about 37 MB (9 MB compressed). The backend is about 53 KB plus a roughly 60 MB Python virtualenv. That leaves around 380 MB for the database and uploads. Images, not data, are what will eat that space.

Why SITE_URL is set at build time

robots.txt, canonical URLs and the Open Graph tags on prerendered pages are generated during the build. Change the domain later and the pages are wrong until you rebuild.

Where things live on the server

Do not run npm install on the server. The bundle already contains everything it needs, and installing would blow the quota. To redeploy, extract the new archive over the frontend folder and restart the Node app.

What to watch on this plan

The point

A hard limit is a specification. Once the 500 MB is written down as a budget, with a line for each item, the design decisions mostly make themselves.

Written from the deployment notes of Chama Cha Mimi na Wewe. The two Python-selector faults it mentions have their own write-up: the cPanel Python selector leaves your app broken in two silent ways.