skip to Main Content

I have my website hosted on a subdirectory, as an example, http://mywebsite/admin, where /admin is the subdirectory

I have my react app (create-react-app production build) uploaded into the /admin subdirectory. I am also using react-router v4 BrowserRouter to have some simple client-side routing performed. Below are some relevant snippets on my react-app:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import { BrowserRouter } from 'react-router-dom';

import './css/custom.css';

ReactDOM.render(<BrowserRouter basename="/admin"><App /></BrowserRouter>, document.getElementById("root"));

inside App.jsx render function

<Route
    path='/students'
    render={(props) =>
        <Students
            {...props}
        />
    }
/>

<Route
    path='/teachers'
    render={(props) =>
        <Teachers
            {...props}
        />
    }
/>

All is well and working when navigating the site including the nested urls which are mainly, as you have seen on the snippets, /admin/students & /admin/teachers.

The main issue: when I am on the /admin/teachers route and I refresh the page, the page does not load anything. Refreshing works when I am on /admin route only. But not when it is /admin/teachers nor /admin/students.

This is not the first time I have built an SPA using react-router v4 hosted on Apache server. I have reused the .htaccess I have on my previous project. This .htaccess also resides at the /admin subdirectory. Below is the .htaccess:

.htaccess

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.html?path=$1 [NC,L,QSA]

This routes all requests to the index.html built by the create-react-app production build. However, upon my investigations, this type of configuration only works on a project that is not on a subdirectory and is not using nested URLs.

Note, that I have 2 .htaccess now. One on root and another one at the /admin subdirectory. Both contain the same .htaccess codes. The reason for this is, I have another SPA running at the root directory.

What am I missing?

How to Properly Configure Apache on a SPA React App that Resides on a Subdirectory that uses Nested URLs built from React Router?

3

Answers


  1. Chosen as BEST ANSWER

    This is where I made a mistake:

    Note, that I have 2 .htaccess now. One on root and another one at the /admin subdirectory. Both contain the same .htaccess codes. The reason for this is, I have another SPA running at the root directory.

    Simply adjusting my root .htaccess and deleting the .htaccess that was inside the /admin subdirectory fixed the main issue described on my question.

    Root .htaccess:

    RewriteCond %{REQUEST_URI} admin
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.*) admin/index.html?path=$1 [QSA,L]
    
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.*) index.html?path=$1 [QSA,L]
    

  2. I actually don’t know the answer to your problem, but I can suggest you to play with Location directive; something like

    <Location "/admin">
      RewriteEngine on
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule ^(.*)$ /admin/index.html?path=$1 [NC,L,QSA]
    </Location>
    

    Hope this helps.

    Login or Signup to reply.
  3. Primarily addressing the OP’s answer, plus some other notes…

    This is where I made a mistake:

    Note, that I have 2 .htaccess now. One on root and another one at the
    /admin subdirectory. Both contain the same .htaccess codes. The reason
    for this is, I have another SPA running at the root directory.

    This wasn’t necessarily a "mistake" – you would just need to make sure you are using the correct directives. In fact, keeping the two .htaccess files separate could be preferable if these are intended to be entirely separate SPAs.

    To have two separate – identical – .htaccess files the important points are:

    • Make the substitution string relative, ie. no slash prefix.
    • Do not include a RewriteBase directive. (Which would override the directory-prefix.)

    When the RewriteRule substitution string is a relative path then the directory-prefix (ie. the location of the .htaccess file) is added back at the end of the rewriting process.

    So, both /.htaccess and /admin/.htaccess would contain identical directives:

    RewriteEngine on
    RewriteRule ^index.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.+) index.html?path=$1 [L,QSA]
    

    HOWEVER, a slight difference with this solution and the OP’s solution is that the admin/ part of the URL-path is not passed to the path URL parameter. You could hardcode this if required. eg. index.html?path=admin/$1. Or account for this in your script.

    The first RewriteRule is just a minor optimisation that prevents further filesystem checks after the request has already been rewritten to index.html.

    Note that I changed (.*) (0 or more) to (.+) (1 or more). This avoids the additional directory check when requesting the root directory. ie. example.com/ and example.com/admin/. You are not rewriting these requests, but instead are reliant on DirectoryIndex index.html being set appropriately – this is the default setting in the server config.

    Since the substitution string (index.php?page=$1) is relative, when in the document root it will effectively rewrite to /index.php and when in /admin/.htaccess it will effectively rewrite back to /admin/index.php.

    Note that the mod_rewrite directives in /admin/.htaccess completely override the mod_rewrite directives in the parent .htaccess file since mod_rewrite directives are not inherited by default (unlike other modules).


    Alternatively, to avoid repetition, whilst still maintaining two separate .htaccess files you could enable mod_rewrite inheritance in the /admin/.htaccess file. For example…

    In the root /.htaccess file:

    RewriteEngine on
    RewriteRule ^index.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.+) index.html?path=$1 [L,QSA]
    

    Then, in /admin/.htaccess you would have the following instead:

    RewriteEngine On
    RewriteOptions Inherit
    

    This inherits the parent mod_rewrite directives as if they were literally copied in-place in the /admin/.htaccess file.

    However, this adds an additional layer of complexity. Apart from the SPA in /admin now being dependent on the root .htaccess file, you now have to be careful that you don’t add any breaking directives to the root .htaccess file.


    Aside:

    RewriteCond %{REQUEST_URI} admin
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.*) admin/index.html?path=$1 [QSA,L]
    
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.*) index.html?path=$1 [QSA,L]
    

    The first condition that checks for "admin" in the URL-path (incidentally, this checks for "admin" anywhere in the URL-path – which isn’t strictly correct) is not required – the check can be performed more optimally in the RewriteRule directive itself. For example:

    RewriteRule ^(admin/.*) admin/index.html?path=$1 [QSA,L]
    

    These two rules can be combined into one if desired, possibly at the expense of some clarity. For example:

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(admin/)?.* $1index.html?path=$0 [QSA,L]
    

    The $1 backreference then contains the optional path-prefix, either admin/ or nothing. And $0 contains the full match.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search