Tricking Laravel to load SPA frontend using basic webhost server

In the quest to leverage ith web hosting that I have already been using for this blog, I wanted to develop and self-host (cheaply) with what I already have. I started to learn Laravel. I think anyone familiar with Laraval knows how much of a beauty this framework is.

With some research, I decided to build my application using Laravel + React. A mix of something new and something I already know to speed things up.

One of the biggest challenges for me is the setup to host the whole application (frontend and backend) using my hostinger service. By design, because it is already running on PHP, the backend part was pretty straightforward. However, the front end was the challenging one. For context, my code was bootstrapped using this laravel-react-purity template which has the structure like this:

/root
  |- /laravel-api
  |- /react-ui

In short, for development, you would have laravel-api running on one localhost port, and react-ui running on a separate localhost port. If you plan to run both on a separate domain (eg. api.yourdomain.com and app.yourdomain.com) there shouldn’t be any issue.

Just that…

I am pretty stubborn. It should be possible to share both frontend and backend. That is where I found out there aren’t many guides with Google search I ended up using fragmented information to piece together this workflow which works so seamlessly with my webhosting server.

Breaking down the challenge

Tricking Laravel’s view to load the react SPA page

With some smart hack, this is how I did it with my GHA script.

  - name: configure-deploy-directory
    run: |
      mkdir deploy
      # deploy laravel as root
      mv laravel-api/* deploy
      # replace to index.php for webserver to detect
      mv deploy/server.php deploy/index.php
      # address htaccess issue
      cp deploy/public/.htaccess deploy
      # rename index.html to blade using web view resource
      mv react-ui/build deploy/client
      mv deploy/client/index.html deploy/resources/views/app.blade.php
      # static file using public url defined by the build steps
      mv deploy/client/* deploy/public

Let’s dive into detail:

Firstly, I treat the laravel-api as the first-class citizen in the deployment. Thus, it goes into the root of the directory.

Then, I created a server.php to emulate Apache’s “mod_rewrite” functionality from the built-in PHP web server. I chose not to commit as index.php is to preserve my development environment.

<?php

/**
 * Laravel - A PHP Framework For Web Artisans
 *
 * @package  Laravel
 * @author   Taylor Otwell <taylor@laravel.com>
 */

$uri = urldecode(
    parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);

// This file allows us to emulate Apache's "mod_rewrite" functionality from the
// built-in PHP web server. This provides a convenient way to test a Laravel
// application without having installed a "real" web server software here.
if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
    return false;
}

require_once __DIR__.'/public/index.php';

Unfortunately, we also need to tweak the htaccess so that are aware of the loading of the /root (index.php). Thus, my setup for the file is as follows:

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Send Requests To Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

Finally, build the react into a static page, and renaming with app.blade.php. This is to trick Laravel into loading the HTML as though is coming from Laravel. All the resources can be access through /public directory. Which is also what I did.

Remember to update the API call to the server accordingly in the config/constant.js

export const API_SERVER = process.env.NODE_ENV === "production"
  ? "/api/"
  : "http://localhost:5000/api/";

Automating the compilation for the web hosting server

Once all the bells and whistles are setup, we are prepared to push everything to the web server. Credit to Stackoverflow for the idea. This is the GHA for it:

- name: push-to-special-branch
  uses: s0/git-publish-subdir-action@develop
  env:
    REPO: self
    BRANCH: hostinger # The branch name where you want to push the assets
    FOLDER: deploy # The directory where your assets are generated
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub will automatically add this - you don't need to bother getting a token
    MESSAGE: "Build: ({sha}) {msg}" # The commit message

What it does, is to push everything into a separate or reserve branch. In my case above, I call it hostinger.

Setup continuous deployment with Hostinger

If the above is done right, just need to follow the steps provided by hostinger.

Conclusion

Every time, when I finish my code, and am ready to deploy to production, I simply just need to trigger the workflow_dispatch and I am done. With this setup, I have eased my step needed to upload to Hostinger and do not need to worry about how to redeploy if I do happen to make some fixes or new features.