Next.js on Shared Hosting

Dave SlackThursday, August 22, 2024

In this article I’ll explain 1 way to install Next.js on a shared hosting that does not actively support it. The hosting I’ve chosen doesn’t even support Node.js. The method I’m going to use will allow SSR and all the other great functionality of Next.js.

Huyton Web Services logo, the test Next.js on Shared Hosting and a server cloud with many clients

To be clear, we can simply bundle the build folder, add it to the server, change the server config and we’re done, but I’m looking to use Server Side Rendering (SSR) for all the reasons explained in the Next.js docs.

Here's what we're going to do:

  1. Install Node Version Manager (NVM)
  2. Install Node.js and Node Package Manager (NPM)
  3. Pull our code from our repository (repo on GitHub)
  4. Install, Build and Run our app with the correct port
  5. Forward traffic on the running server
  6. Create a script to run the app if it’s not running
  7. Run the script every minute with Crontab

This may take a few days to setup depending on if you have a repo containing your app and a working local or not. If you don’t, I’d suggest creating an app locally (Hello world will do), test it, get it into a Git repo and push to GitHub.

The first thing we need to do is choose a Hosting and Domain. I have chosen Hostinger because I have other sites on it already. Unfortunately, upon reading the documentation, Hostinger does not support Node.js on the shared server. Next.js will not work with this host… or will it? The site you are reading this on is using Node.js and Next.js on Hostinger so I’d say it works great.

Node.js works just fine on most shared hosting and is extremely performant, so we’ll install that first.

SSH

We need access to the command line, so we need to enable SSH. Some shared hosts do not give access to the command line, if that is the case you might be out of luck and will not have the ability to run Next.js in this way.

In Hostinger, go to the dashboard for the website you want to install Next.js, then on the left hit Advanced and SSH. From here you’ll need to Enable SSH and set it up on your local (there are instructions on the SSH page if you’re not sure).

Once you have SSH working, login to your server in your terminal of choice and make sure you know where the home folder is for your website. It will be something like '~/domains/DOMAIN/public_html', but use 'pwd' in the terminal to find out.

NVM, Node.js and NPM

CD back into your home folder (you don’t want this in the public folder) and install Node Version Manager (nvm) with:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash

NB. Any issues take a look at the nvm readme. You may have issues with ownership of folders (I did) so you’ll need to use 'chmod' or 'chown' on some of the directories to fix this, but it shouldn’t be too much of a problem.

Once the installer has finished it will tell you to either run a script or close the terminal and reopen, do that then check in nvm is installed with:

nvm -v

My terminal says `0.40.0` but yours will say the latest version. 

Next, we need to install node, let’s install the latest stable version with: 

nvm install stable

At the end of the installation, the console will tell you the version you installed, mine was v22.6.0. To test this run:

node -v

You will see your version (v.22.6.0), now, to see if you have Node Package Manager (npm) installed correctly run:

npm -v

I got 10.8.2 

Node.js and Node Package Manager are now installed.

Repo

We need a repo containing our Next.js website, I’m using Git as my VCS and GitHub is used to manage and share my versions. I’m using private directories for some of my repos, so it might look a bit empty on our GitHub page. I’m also using Gitflow with master and develop branches as my branching strategy, but you can use any strategy.

To install Next.js I used npx create-next-app@latest then answered the questions as:

  • What is your project named? ... frontend
  • Would you like to use TypeScript? ... No
  • Would you like to use ESLint? ... Yes
  • Would you like to use Tailwind CSS? ... No
  • Would you like to use `src/` directory? ... No
  • Would you like to use App Router? (recommended) ... Yes
  • Would you like to customize the default import alias (@/*)? » No

If you don’t have a repo for your code, I highly suggest you get your local version of a Next.js project working and push it to a repo. Then you will know there are no issues with your code before you try to get it working on a server.

At this point you might want to make a change to your package.json file so you know you have the correct port when running live. Look for the line:

"start": "next start",

and to run on port 4040 (or any number not on this list), change it to:

"start": "next start -p 4040",

Test your project on local (if you use npm run dev there will be no change) and push it into your repo and server as usual. Remember the port number though, we’ll need this soon.

Install, Build & Run

We need to make sure your app is working correctly on the server so let’s go ahead and do that.

In your terminal you’ll need to go to your public directory for your hosting, so something like '~/domains/DOMAIN/public_html' and pull in your latest code from your repo. Next, you’ll need to install all the packages you for your software with the usual install, build and run:

npm i
npm run build
npm run start

After running these your app should be running, if not, fix any bugs you might have and try again until it runs, and you have no access to the terminal (it's running your app, so you can't interact with it). You won’t have access to see the app just yet, we’ll get to that in a moment.

Once it’s all running Ctrl+c and answer yes to quit out. Let’s get it running on the domain.

Server

I use Apache and have not used Nginx, so the config below has not been tested. If you do test it, please comment, and let me know how it went.

Apache

Chances are you will have an Apache server running so we’ll need to tell Apache to route any incoming traffic to the app. We’ll create a .htaccess (note the dot at the beginning) file to do this, but first we need to make sure the file doesn’t end up in our repo or cause issues later, so, we’ll remove it from our repo before we create it. Open up your .gitignore (notice the dot at the beginning) file in the root of your project (create it if it does not exist) and add .htaccess to the bottom on it’s own line (if it’s not already in) and commit the file and push it. Now pull the app down to the server and we’re ready to add our Apache rules.

Create a file in the public directory of your server called .htaccess and add in the code below using nano or vim or whatever you use for text files. Use:

nano .htaccess

Add in the code below:

RewriteEngine On
RewriteCond %{HTTP_HOST} !^$
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTPS}s ^on(s)|
RewriteRule ^ http%1://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=302,L,NE]
RewriteRule ^$ http://127.0.0.1:4040/ [P,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ http://127.0.0.1:4040/$1 [P,L]

This file will do 3 things, it will force www to your domain (good for SEO), it will force SSL so you will need to make sure your domain has an SSL cert installed, if you don’t you’ll want to get one (Hostinger will do it automatically as will most shared hosts these days) and lastly it will route traffic from your site to your local on your server @ port 4040.

If you are not running your Next.js app on port 4040 you’ll need to change the port on lines 8 and 11 to the port you added to your package.json previously.

If you only want to do the basic redirect then you can comment out lines 2 through 7, inclusive, with a hash (#).

NginX

In your terminal open the file /etc/nginx/nginx.conf with nano or vim (or the text editor of your choice) and look for the location / and change it to the code below:

location / {
  if ($http_host !~ "^$"){
    rewrite ^(.*)$ http%1://www.$http_host$request_uri redirect;
  }
  rewrite ^(.*)$ https://$http_host$request_uri redirect;
  rewrite ^/$ http://127.0.0.1:4040/ redirect;
  if (!-e $request_filename){
    rewrite ^(.*)$ http://127.0.0.1:4040/$1 redirect;
  }
}

You'll need to change the port to the one in your package.json if you didn't use 4040

Build & Run

You have installed all the software; your app is running and is version controlled and you have set up your server. This is the point your app should be able to run on your shared hosting. Build again (just to make sure no changes have sneaked in: 

npm run build

Once built run: 

npm run start

You will see the Next.js messages and all should look fine. Open your browser and go to your domain, you should see your app. If have any errors at this point (check your terminal), you’ll need to fix them, comment here if you need help.

Press Crtl+c then y and enter to get your terminal back and your app will stop working. Even though you had the app working if you close the terminal, your app will stop. Having to leave your computer on and the terminal open is not ideal, so let’s get the app running on its own and keep it running.

Bash script

This is my solution to the problem, there are other ways to do this and maybe better ones. If you have another way to do this let me know. 

We have 3 goals for this section, we need to:

  1. Run the app without using the terminal.
  2. Start the app if it stops.
  3. Rebuild the app when we update it.

Our first job is to create a script that will check if the app is running, so outside your public directory (I suggest the directory below) create a new file like:

nano ~/domains/DOMAIN/app_run.sh

In the file we need to add the code:

# !/bin/bash
export PATH=/bin:/usr/bin:/NVM_DIR/.nvm/versions/node/v20.11.0/bin
NAME="npm run start"
RUN=`pgrep -f "$NAME"`
current_date_time=$(date +"%Y-%m-%d %T")
if [ "$RUN" == "" ]; then
  echo "$current_date_time - Script is not running"
  cd /ROOT_TO_PUBLIC/public
  npm run build
  nohup npm run start > /dev/null &
  exit
else
  echo "$current_date_time - Script is running"
fi

You’ll need to make a change at line 3, NVM_DIR must be the location from root to nvm (pwd is your friend here) and you’ll need to change v20.11.0 to the correct version of node you are using.

On line 10 you’ll need to change this to the root of your public folder.

What this script does is set up some vars, check if the task called `npm run start` is running (you can use `top` or `htop` to check for this). If the task is running we echo out the time, but if it’s not running we build the app then run it detached (through nohup), we then exit.

Try running this with:

bash app_run.sh

Your app should run fine, and you should have access to the terminal after it has run. You can go to the domain and after about minute you can refresh your browser and see your app. Back in your terminal if you run top you should see the worker and the npm run start tasks. Ctrl+C to get out of `top`.

You’ll want to run this script every minute to make sure your app is never down for longer than a minute or so. To do this we create a Cron job.

Cron

We can use cron job to run any bash script at intervals like every day at a time, or once a month or every hour or in our case, every minute. First thing is to check your hosting has cron access (or something similar). On Hostinger, we would click Advanced -> Cron Jobs.

Before we add the cron, let’s make sure we add the correct job.

The script we wish to run is something like:

bash ~/domains/DOMAIN/app_run.sh

Your script will be in a different place, so use pwd to find the directory. Cron syntax is:


Min  Hour Day  Mon  Weekday
*    *    *    *    *  command to be executed
┬    ┬    ┬    ┬    ┬
│    │    │    │    └─  Weekday  (0=Sun .. 6=Sat)
│    │    │    └──────  Month    (1..12)
│    │    └───────────  Day      (1..31)
│    └────────────────  Hour     (0..23)
└─────────────────────  Minute   (0..59)

Each star is a number so, if we want every hour at 27 minutes, we would use 27, every hour, every day, every month, every day of the week so:

27 * * * * bash ~/domains/DOMAIN/app_run.sh

We are looking to do every minute so we use:

* * * * * bash ~/domains/DOMAIN/app_run.sh

Back on Hostinger we add our script to run in the first box ‘Command to run’ then set everything else as a star then save.

If you wish to use the command line use:

crontab -e

A file will open and at the bottom add the line:

* * * * * bash ~/domains/DOMAIN/app_run.sh

Change to your directory and save the file.

Finishing up

Make sure you don’t have anything running in the terminal, check top to make sure you have no npm run start task running (use kill -9 PID to stop the task). 

Run top or htop and at after a minute you will see a new task start up called npm run start. You can then check your browser and your app will show.

On Hostinger, to make changes to prod you would merge to the master branch as usual, then “Stop running processes” button (under Dashboard-> Hosting resources usage->See details). After a minute or so the new version would be live. Simple.

Next steps

I’d like to add a check in the bash script that checks the hash of the master branch and if it’s changed, rebuild. 

If you have anything to add, or if you’ve added anything that might be useful to others, please let us know with a quick comment. Thanks.
 

Leave a comment

If you don't agree with anything here, or you do agree and would like to add something please leave a comment.

We'll never share your email with anyone else and it will not go public, only add it if you want us to reply.
Please enter your name
Please add a comment
* Denotes required
Loading...

Thank you

Your comment will go into review and will go live very soon.
If you've left an email address we'll answer you asap.

If you want to leave another comment simply refresh the page and leave it.

We use cookies on Huyton Web Services.
If you'd like more info, please see our privacy policy