8

I have a single page website with URL parameters that should still open the single index.html (made with Vue.JS and Vue.JS Router). For example, there is www.example.com/, www.example.com/user, and www.example.com/user/project/myproject. Now, as you might expect, I'm not getting this to work with my .htaccess file.

I've tried a lot of ways to get this to work:

Order allow, deny
Deny from all
<FilesMatch ^(index\.html)?$>
  Allow from all
</FilesMatch>

IndexIgnore *
DirectoryIndex index.html

Which didn't do anything. Then I replaced the content of my .htaccess file with this:

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

Options All -Indexes

It changed something, but not the correct thing. As www.example.com/user gave an HTTP error 404 as the directory isn't found obviously.

My goal

With a tree like this:

├───assets
├───downloads
│index.html
│robots.txt

I want to be able to access only index.html that is used for any parameter combination. Access to assets and downloads should be accessible as people should be able to view the page correctly and to download content if they want to. assets contains static resources, likewise for downloads.

I have looked at a ton of sources, but none seem to give me the perfect outcome. I'm hoping that you can help me out.

Thanks for your time!

UPDATE

Combining MrWhite's and Lacek's answer seems to do the job, but there is a significant issue now. All files seem to be loaded in with the content of index.html. So every image, js, CSS, etc. has an HTML, BODY, HEAD, DIV, SCRIPT (and so on) tag as data instead of their actual data, like pixel data for images.

This is what my .htaccess looks like now:

Options All -Indexes
DirectoryIndex index.html

<Files index.html>
    AcceptPathInfo On
</Files>

RewriteEngine On

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

And the virtual host config (domain censored):

<VirtualHost *:80>
    ServerAdmin admin@example.com
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example/html

    AliasMatch "/.*" "/var/www/example/html/index.html"
    Alias "/assets"  "/var/www/example/html/assets/"
    Alias "/downloads"  "/var/www/example/html/downloads/"

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Check the comments below and below the answers for more context.

Sources

ServerFault - 301 redirect entire domain to a single page .HTACCESS

ServerFault - htaccess rewriterules for multiple pages with parameters

ServerFault - Having issues with my websites htaccess

HTAccess Guide

HTAccess cheat sheet

HTAccess Cheat Sheet - Duck Duck Go

M Zeinstra
  • 81
  • 1
  • 8
  • What are `assets` exactly? (Are they your static resources like CSS, JS and images, referenced from your client-side HTML?) How is `index.html` expecting to receive the request? In your directive above you are passing the URL as _additional pathname information_ (path-info). However, the _default_ handler for text/html files does not accept path-info and will trigger a 404 (regardless of whether that "directory isn't found"). What version of Apache are you using? – MrWhite Nov 15 '19 at 11:33
  • @MrWhite `assets` are indeed static resources. I'm using Vue.JS Router to handle the routing, so I'm not really sure unfortunately. The Apache version is Apache/2.4.29 (Ubuntu). – M Zeinstra Nov 15 '19 at 11:41
  • From what I know, Vue.JS Router scans the URL (to figure out what it has to display) and manages the routing history of the current session. – M Zeinstra Nov 15 '19 at 13:06

2 Answers2

4

assets are indeed static resources.

If your "assets" are client-side static resources such as CSS, JS and images then you can't block them in .htaccess as they need to be publicly accessible for the client-side HTML to be able to access. (?)

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

This is passing the requested URL as additional pathname information (ie. path-info) to index.html, eg. Request /foo/bar and it will send the request to index.html/foo/bar. The issue here is that the default text/html handler does not accept path-info by default and will trigger a 404.

But Vue.JS may not require this as path-info anyway - it may simply "scan the URL" as you suggest. In which case, you can simplify the directives something like:

Options All -Indexes
DirectoryIndex index.html

RewriteEngine On

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

This is a more "standard" front-controller. The QSA flags are not required here.

This rewrites everything that does not map to a file or directory to index.html. The first RewriteRule is simply an optimisation to prevent the rewritten request to index.html being retested.

You don't need to explicitly rewrite the root request (ie. RewriteRule ^$ index.html/) since mod_dir will do this via an internal subrequest having specified DirectoryIndex index.html.

MrWhite
  • 11,643
  • 4
  • 25
  • 40
  • That makes a lot of sense! Thanks for all the information. Unfortunately, it doesn't seem to do what I want.. When I navigate to `www.example.com/user` it returns a HTTP Error 404. Could that mean Vue.JS Router does something else than I think, or is this something that should be corrected in the `.htaccess` file? – M Zeinstra Nov 15 '19 at 15:10
3

Try this:

Alias "/download"  "/wherever/your/app/is/download/"
AliasMatch "/.*" "/wherever/your/app/is/index.html"

These directives will route everything but /download/* to the index.html file.

These have to be included in the VirtualHost config, not in the .htaccess file. Note that with the Alias directive, you have to use a full filesystem path, not the relative path under the webroot.

Also, as @MrWhite pointed out, you might want to explicitly enable the usage of PathInfo for a html page, like this:

<Files index.html>
    AcceptPathInfo On
</Files>
Lacek
  • 6,585
  • 22
  • 28
  • The AliasMatch did it for me! Very nice. Actually, it was a combination of your answer and the answer of @MrWhite. Your solution was the key ingredient :) – M Zeinstra Nov 15 '19 at 17:47
  • Actually... Now all files are loaded as `index.html`. So, for example, my downloads return the content of `index.html`.. How is this possible? My virtual host config has `AliasMatch "/.*" "/path/index.html"` , `Alias "/assets" "/path/assets/"` , and `Alias "/downloads" "/path/downloads/"`. So, according to your answer this shouldn't happen. – M Zeinstra Nov 15 '19 at 18:05
  • The order of the `Alias*` directives is important If the `AliasMatch /.*` is the first, then it will handle everything, since `/.*` matches, and the processing stops there. That line should be the last, since it matches everything. – Lacek Nov 18 '19 at 11:45