Search code examples
wordpress.htaccesshttp-redirect

htaccess: I can't redirect to nested subfolders correctly on the WordPress app


I have a multilingual WordPress site that has two languages: en and tr, It's site A.

I wanna put my other WordPress site inside /tr/shop/ folder, It's site B.

Just need only these rules:

  • /en/anything/ goes to site A (also /en/)
  • /tr/anything/ goes to site A (also /tr/)
  • /tr/shop/ goes to site B inside the /tr/shop subfolder.

Currently:

  • If visitors go to /en/ or /en/anything, It's OK.
  • If visitors go to /tr/shop/, they see site B, It's OK. Because the folder exists.
  • Visitors who go to /tr/* see a 404 error. Because /tr/ is a physical folder but empty. It's just a language prefix, not a physical folder. I need to exclude /tr/ folder from requests.

How can I do it?

My current .htaccess is below but not working.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /

# Exclude /tr/ from rewriting
RewriteRule ^tr(/.*)?$ - [L]

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# Beginning of shop part
RewriteRule ^tr/shop(/.*)?$ /tr/shop/$1 [L]
RewriteRule ^tr(/.*)?$ /index.php [L]
# End of shop part
</IfModule>

Solution

  • # Exclude /tr/ from rewriting
    RewriteRule ^tr(/.*)?$ - [L]
    

    Since /tr/ is a physical directory, instead of "excluding" /tr/ from the rewrite, you should make this a special case and rewrite /tr/ (only) to /index.php instead (since this rewrite would otherwise be excluded from the rule that follows). Currently, this rule excludes /tr/<everything> from being rewritten to the front-controller, so you will naturally get a bunch of 404s. (Although I would expect /tr/ - the directory itself - to trigger a 403 if mod_autoindex is installed.)

    Then, there shouldn't be anything extra you need to do. You don't need the last two rules (# Beginning of shop part) - which are in the wrong place anyway - since /tr/<something> would naturally be rewritten to /index.php by the main rule and /tr/shop/<anything> would be handled by the /tr/shop/.htaccess file (overriding the root). (I'm assuming /tr/shop/ has it's own .htaccess file; it should.)

    Your complete .htaccess file would then become:

    RewriteEngine On
    
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    
    # Override: Rewrite "/tr/" to the front-controller
    RewriteRule ^tr/$ index.php [L]
    
    RewriteRule ^index\.php$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.php [L]
    

    Note that I removed the RewriteBase / directive and slash prefix on the substitution strings. The <IfModule> wrapper is also not required.

    Maintaining the default WordPress code block

    HOWEVER, the above modifies the default WordPress code block (between the # BEGIN WordPress and # END WordPress comment markers) which is not recommended. There is no need to modify the WP code block. (Having said that, if you are writing this code by hand then the code I've written above is preferable.)

    For example, use the following instead if you which to maintain the default WP code block:

    # Override: Rewrite "/tr/" to the front-controller
    RewriteRule ^tr/$ index.php [L]
    
    # BEGIN WordPress
    # (Warning about not manually editing the code in this section)
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    
    RewriteRule ^index\.php$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
    </IfModule>
    # END WordPress
    

    There is no need to repeat the RewriteEngine On directive before the first rule, since this directive already occurs later in the WordPress code block. (It's actually the last instance of this directive that controls the entire file.)


    Alternatively, install the "shop" in a different directory

    You don't need to install the "shop" in a physical directory that matches the URL-path. You could install the shop in a /myshop subdirectory (not /tr/shop/) and internally rewrite requests from /tr/shop/ to /myshop. (I just used shop and myshop to highlight the difference.)

    However, you would then need to prevent direct access to /myshop/. (It's unlikely that this would be accessed, but not impossible.)

    Then, in the root .htaccess file, before the WordPress code block:

    # Rewrite "/tr/shop/<anything>" to "/shop/<anything>"
    RewriteRule ^tr/shop/(.*) myshop/$1 [L]
    
    # BEGIN WordPress
    # :
    

    In the /myshop/.htaccess file you should add the following:

    # Prevent direct access to this directory
    RewriteCond %{THE_REQUEST} \s/myshop/
    RewriteRule ^ - [F]
    

    A minor caveat with this approach is that /tr/shop (no trailing slash) would result in a 404, rather than being redirected to /tr/shop/). If that is an issue then you can manually implement this as an additional redirect. (Ordinarily, mod_dir does this for you. But if you let mod_dir do this here then it will expose the "hidden" /myshop subdirectory.)