49

I have a small number of static sites where I simply want to hide the .html extension:

  • the URL /foo fetches the static file /foo.html
  • the browser still displays the URL /foo

The client can then send out bookmarks in the style example.com/foo rather than example.com/foo.html.

It sounds very simple, and I've used mod_rewrite happily before (say with WordPress or for redirects), but this is proving much harder to crack that I thought. Perhaps I'm missing something really obvious, but I can't find a solution anywhere and I've been at it all day!

We run our own server, so this can go wherever is the best place.

Addendum

The solution checked below worked fine. Then after running the site awhile I noticed two problems:

  1. all pages began to appear unstyled. I reloaded, cleared the cache, etc., but still no-style. I've had this trouble before, and can't locate the source.

  2. There's a directory AND an HTML file named 'gallery', so the /gallery link shows a directory listing instead of the HTML file. I should be able to sort that one, but further tips welcome :-)

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Dave Everitt
  • 17,193
  • 6
  • 67
  • 97

8 Answers8

58

Try this rule:

RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule !.*\.html$ %{REQUEST_FILENAME}.html [L]

This will rewrite all requests that can be mapped to an existing file when appending a .html.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
  • 1
    Thanks! Just changed the pattern to '(.[a-z]+)' as all the html files contain only those chars. New problems now, worst one is no CSS (this initially appeared on reload, then toggled styles on and off, now all files are unstyled so I guess the earlier views were getting a cached CSS file?). – Dave Everitt Jan 03 '10 at 17:48
  • @DaveEveritt I'm having the same CSS problem, only when I append a trailing slash to the URL, in which case all relative links are considered to be in a subdirectory, including the CSS. Did you find a way to fix it? – Lazlo Jan 12 '12 at 14:47
  • 1
    @LazloBonin One solution is not to use absolute paths instead of relative paths. Or explicitly set a base URL with an appropriate path so that relative paths are resolved from the base URL path instead of the current URL path. – Gumbo Jan 12 '12 at 15:25
  • I had a number on the file name, and it did not work. Just alerting. – gutierrezalex Nov 26 '12 at 14:58
  • @Lazlo - (late reply, I know) no I didn't solve it, and gave up after attempts produced very unpredictable results. Then swapped to using static site generator nanoc, which gets around the issue I wanted to solve (with an index file in a directory for each page). – Dave Everitt Aug 23 '13 at 12:46
  • Shouldn't the `!.*\.html$` part in the RewriteRule be changed to maybe just `^`. What is the benefit of it? You are already checking if the file exists in the `RewriteCond`, and you aren't using the result of the pattern in the substitution string. It does have a drawback, if file named for example `filename.html.html` you cannot open it with `/filename.html` (Though I don't know why would anyone would have this use case, but it is interesting to note) – Dan Apr 21 '15 at 17:29
  • Quick question, what is the purpose of the `!` in your RewriteRule? – Ash Menon Jun 14 '15 at 11:34
  • 1
    @AshMenon That’s the negation operator: Only apply if the following is not matched. – Gumbo Jun 14 '15 at 11:35
  • So if I'm understand it correctly, your first line is a Rule to check that the file (requested filename with ".html" appended) exists, and the second line applies to anything that DOESN'T end with ".html" and rewrites it to the appended equivalent? – Ash Menon Jun 18 '15 at 08:57
  • 2
    @AshMenon The rule means: only append `.html` if it's not already ends with `.html` and if the resulting file exists. – Gumbo Jun 18 '15 at 09:48
  • What file do I change? I don't know where to paste this code. Thanks in advance! –  Jul 17 '16 at 10:49
  • @MichaelJancen-Widmer this code goes in your `.htaccess` file. – Sauce Nov 16 '16 at 12:06
  • Don't forget to `RewriteEngine on` – Leo Mar 13 '17 at 22:47
41

The previous answers don't check if the requested path is a directory.

Here is the full rewrite condition which doesn't rewrite, if requested path is a directory (as stated by the original question):

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d          # is not directory
RewriteCond %{REQUEST_FILENAME}\.html -f     # is an existing html file
RewriteRule ^(.*)$ $1.html                   # rewrite index to index.html

To be SEO friendly and avoid double content, redirect the .html URLs:

# Redirects example.com/file.html to example.com/file
RewriteCond %{REQUEST_FILENAME} !-d          # is not directory
RewriteCond %{REQUEST_FILENAME}\.html -f     # is an existing html file
RewriteCond %{REQUEST_URI} ^(.+)\.html$      # request URI ends with .html
RewriteRule (.*)\.html$ /$1 [R=301,L]        # redirect from index.html to index

If you need the same for scripts take a look here: How can I use .htaccess to hide .php URL extensions?

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Stefan Profanter
  • 6,458
  • 6
  • 41
  • 73
  • Do I need to include both of these rules or just the last one? The first one loads the /index and shows the index.html file. But including the second block doesn't redirect /index.html back to /index for the duplicate content. – Luke Jul 18 '13 at 14:33
  • 3
    You need both rule blocks. The first one rewrites `index` to `index.html` internally so the user can't see it. The second block does a 301 redirect from `index.html` to `index`. – Stefan Profanter Jul 18 '13 at 18:51
  • 8
    Note that - at least as of Apache 2.2.23 - inline comments are apparently NOT supported, so the above definitions will break the `.htaccess` file and result in a `500` error, unless you remove the inline comments. – mklement0 Aug 21 '13 at 02:08
  • 5
    If I use your definitions unmodified, a path ending in '.html' is NOT redirected. If I remove `RewriteCond %{REQUEST_FILENAME}\.html -f` from the *2nd* block, I end up with a redirect loop (as of Apache 2.2.23). – mklement0 Aug 21 '13 at 02:19
  • If I understand the OP's question correctly, he's looking for a *file* to take precedence over a *directory* of the same name; your solution does the opposite. – mklement0 Aug 21 '13 at 02:29
  • 5
    The SEO solution above does not work (as a couple of others have noted). Try `RewriteCond %{THE_REQUEST} ^[A-Z]+\ (/[^\ ]*)\.html[?\ ]` followed by `RewriteRule (.*)\.html$ /$1 [R=301,L]` instead - works for me :) – Paddy Mann Nov 25 '15 at 13:52
  • 1
    @PaddyMann, why do you need the first `RewriteCond`? It apparently checks to see if the request ended in `.html`. But isn't that what the rule part of the `RewriteRule` itself does? Isn't the `RewriteCond` redundant? – Garret Wilson Jun 26 '16 at 22:32
17

The accepted solution do not works when the website is configured with a virtual host / document root.

There is the solution I used:

RewriteEngine on
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}.html -f
RewriteRule !.*\.html$ %{REQUEST_FILENAME}.html [L]
Lukasz Dynowski
  • 11,169
  • 9
  • 81
  • 124
Gael Lafond
  • 519
  • 6
  • 5
4

Look at this post http://alexcican.com/post/how-to-remove-php-html-htm-extensions-with-htaccess/ I haven't tried it yet but the demonstration seems pretty convincing.

Options -MultiViews 
RewriteEngine On 
RewriteCond %{REQUEST_FILENAME} !-d 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteRule ^([^\.]+)$ $1.php [NC,L]
itsazzad
  • 6,868
  • 7
  • 69
  • 89
Syed Priom
  • 1,893
  • 1
  • 21
  • 22
4

Wow, I have seldom seen such an issue for which there exists so many "solutions" on the web, where people just throw up what "works for them" but for which few take time to read the documentation to figure out what it does. Many of the solutions given here don't work for virtual hosts, for example.

After much cross-referencing and reading, I want to contribute my own solution that "works for me". Hopefully it works for you, too. I didn't create it from scratch; I was inspired by all the other contributions (even though most of them did not "work for me" without modification).

RewriteEngine on

#if foo requested, return foo.html contents
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}\.html -f
RewriteRule ^(.*)$ $1.html [L]

#redirect foo.html to foo
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
RewriteRule ^(.+)\.html$ $1 [R,L]

The [R] flag by default does a temporary (302) redirect; if you want a permanent redirect, use R=301 in place of R.

Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
3

To remove .html extension from .*.html requests, you can use the following script in root/.htaccess :

RewriteEngine On
RewriteBase /
#1) externally redirect "/file.html" to "/file"
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s([^.]+)\.html [NC]
RewriteRule ^ %1 [R=301,L]
#2) rewrite  "/file" back to "/file.html"
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.*?)/?$ $1.html [NC,L]
Amit Verma
  • 40,709
  • 21
  • 93
  • 115
  • thanks, although this is an old question and I use [Nanoc](http://nanoc.ws/) (a static site generator) for sites like this. I tried this, but the spin-off problem remains - my styles completely disappear. – Dave Everitt Jun 25 '16 at 09:05
  • You are welcome @DaveEveritt ! To fix your style, js ,images and other relative resources on rewritten urls, you can add a base tag to head section of your webpage `` for more info..read my answer on this post http://stackoverflow.com/questions/31241701/seo-friendly-url-css-img-js-not-working/31241992#31241992 – Amit Verma Jun 25 '16 at 09:19
  • Tried again but still no joy. It's an old site and I was using SSI (so changed .html to .shtml in your example) and had already moved all the main subsections into their own directories, but non-index .shtml pages inside those directories get their containing dir stripped from the URL so 404, and "/styles/body.css" still fails to load throughout the site. Anyway, thanks for the tips, but I'll abandon this now as the site will be remade at some point! – Dave Everitt Jun 26 '16 at 17:49
3
  • the url /foo fetches the static file /foo.html
  • the browser still displays the url /foo

Apache can do this without mod_rewrite, see documentation:

Multiviews

The effect of MultiViews is as follows: if the server receives a request for /some/dir/foo, if /some/dir has MultiViews enabled, and /some/dir/foo does not exist, then the server reads the directory looking for files named foo.*, and effectively fakes up a type map which names all those files, assigning them the same media types and content-encodings it would have if the client had asked for one of them by name. It then chooses the best match to the client's requirements.

Source: http://httpd.apache.org/docs/current/content-negotiation.html

sɐunıɔןɐqɐp
  • 3,332
  • 15
  • 36
  • 40
StefanKaerst
  • 331
  • 2
  • 6
1

Here is an example which allows us to store the file on disk as:

foo.html.php

But in the browser, refer to it as

foo.html

To make this work for you, I think you would just need to modify it a bit to match your existing requests, and check for an actual file in place with the .html extension.

 # These are so we do not need the .php extension
 RewriteCond %{REQUEST_FILENAME} (\.xul|\.html|\.xhtml|\.xml)$',
 RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}.php -f',
 RewriteRule ^(.*)$ $1.php',
gahooa
  • 131,293
  • 12
  • 98
  • 101