177

How to remove .html from the URL of a static page?

Also, I need to redirect any url with .html to the one without it. (i.e. www.example.com/page.html to www.example.com/page ).

RavinderSingh13
  • 130,504
  • 14
  • 57
  • 93
Dave
  • 3,328
  • 7
  • 26
  • 30
  • 2
    By "remove .html", do you mean "not require .html to be present"? – Lightness Races in Orbit Apr 20 '11 at 12:26
  • @Tomalak: Yes and also redirect urls with ".html" to the ones without. My problem is that this results to infinite redirection. My current setup allows www.example.com/page.html and www.example.com/page to be both accessible which isn't SEO friendly. – Dave Apr 20 '11 at 22:55
  • 3
    See http://stackoverflow.com/questions/5574442/why-does-this-cause-an-infinite-request-loop, http://stackoverflow.com/questions/5573485/php-htaccess-pretty-url-in-reverse and http://stackoverflow.com/questions/5639367/mod-rewrite-recursive-loop – Lightness Races in Orbit Apr 20 '11 at 23:33
  • @Tomalak: Thanks for the tips. Reading mod_rewrite's documentation was very helpful. – Dave Apr 22 '11 at 04:58
  • Also see this `how to remove HTML and PHP` https://helponnet.com/2020/02/04/remove-html-and-php-extension-with-htaccess-rewriterule-url-rewriting-tips/ – Amit Verma Mar 12 '22 at 07:12

18 Answers18

148

To remove the .html extension from your urls, you can use the following code in root/htaccess :

RewriteEngine on


RewriteCond %{THE_REQUEST} /([^.]+)\.html [NC]
RewriteRule ^ /%1 [NC,L,R]

RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^ %{REQUEST_URI}.html [NC,L]

NOTE: If you want to remove any other extension, for example to remove the .php extension, just replace the html everywhere with php in the code above.

Also see this How to remove .html and .php from URLs using htaccess .

Amit Verma
  • 40,709
  • 21
  • 93
  • 115
145

I think some explanation of Jon's answer would be constructive. The following:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

checks that if the specified file or directory respectively doesn't exist, then the rewrite rule proceeds:

RewriteRule ^(.*)\.html$ /$1 [L,R=301]

But what does that mean? It uses regex (regular expressions). Here is a little something I made earlier... enter image description here

I think that's correct.

NOTE: When testing your .htaccess do not use 301 redirects. Use 302 until finished testing, as the browser will cache 301s. See https://stackoverflow.com/a/9204355/3217306

Update: I was slightly mistaken, . matches all characters except newlines, so includes whitespace. Also, here is a helpful regex cheat sheet

Sources:

http://community.sitepoint.com/t/what-does-this-mean-rewritecond-request-filename-f-d/2034/2

https://mediatemple.net/community/products/dv/204643270/using-htaccess-rewrite-rules

binaryfunt
  • 6,401
  • 5
  • 37
  • 59
  • 20
    Superb diagram to help explain the answer. – Ric Sep 26 '17 at 08:37
  • The tip on the 301s and the browser cache is what solved my issues. – bgfvdu3w Feb 08 '18 at 17:10
  • @KnocksX I am no longer a webmaster and am not in a position to be able to help – binaryfunt Sep 10 '19 at 11:32
  • 2
    Nice graphic, but like the answer it references, this one misunderstands the actual question, and assumes all the files are saved without the `.html` extension. See [my answer](https://stackoverflow.com/a/63803841/2652785) for a more thorough explanation. – Kal Sep 09 '20 at 02:32
  • Does not work for me. How can I debug it? – Black Dec 01 '20 at 15:17
  • Try Lukasz's answer: https://stackoverflow.com/a/11813084/3217306, I think it helped me – binaryfunt Dec 02 '20 at 11:01
81

This should work for you:

#example.com/page will display the contents of example.com/page.html
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.+)$ $1.html [L,QSA]

#301 from example.com/page.html to example.com/page
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.*\.html\ HTTP/
RewriteRule ^(.*)\.html$ /$1 [R=301,L]
Łukasz Habrzyk
  • 811
  • 6
  • 2
  • 4
    I was getting a 404 in Godaddy with this code and I fixed it putting: Options +FollowSymLinks -MultiViews -Indexes at the very top. – Labanino Feb 13 '14 at 13:10
  • 1
    I think this is the best and most complete answer, thanks! – Arian Nov 26 '17 at 11:09
  • I tried to do this in localhost, but it's not working, is there any other thing i need to do, do i have to link the .htaccess file, or how does the page recognise it? – Pianistprogrammer Dec 28 '17 at 10:25
  • How do I add .php extension to #301 from example.com/page.html to example.com/page , is it possible? –  Jul 10 '19 at 12:29
  • This worked for me on GoDaddy too (without having to add the code that @Labanino stated). – Supertecnoboff Mar 03 '23 at 11:24
76

With .htaccess under apache you can do the redirect like this:

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

As for removing of .html from the url, simply link to the page without .html

<a href="http://www.example.com/page">page</a>
Jon Skarpeteig
  • 4,118
  • 7
  • 34
  • 53
  • 29
    This doesn't do anything for me. Is there some reason it wouldn't work? – Michael Yaworski Feb 02 '14 at 04:02
  • Do you have an actual file for the requested link? That would trigger the `!-f` – Martijn Mar 15 '18 at 10:21
  • 1
    @Martijn, I think that's the point—that you have a file at `/page.html`, but you want to link to it with `/page`. I suspect this answer misunderstood the question, and assumed the OP was saving his pages without the `.html` extension (which, as I read it, wasn't the case.) – Kal Sep 08 '20 at 08:19
29

You will need to make sure you have Options -MultiViews as well.

None of the above worked for me on a standard cPanel host.

This worked:

Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.html [NC,L]
Bradley Flood
  • 10,233
  • 3
  • 46
  • 43
  • Out of all the answers above this one finally worked.. I believe because my site is hosted on godaddy with cPanel. The key is Options -MultiViews – Brad Vanderbush Nov 20 '18 at 20:28
  • Yes, nothing in this section works expect this answer! You saved the day! – Nobody Nov 10 '19 at 08:28
  • Thanks mate. I'm not sure why the others didn't work out as expected. – Nanoo Jun 08 '20 at 19:14
  • How can I put a slash at the end of the url ? site.com/test is working, but site.com/test/ is not... Edit: seems that RewriteRule ^([^\.]+)/$ $1.html [NC,L] will do the trick. Is that ok ? – Andrei Dec 15 '21 at 17:19
24

For those who are using Firebase hosting none of the answers will work on this page. Because you can't use .htaccess in Firebase hosting. You will have to configure the firebase.json file. Just add the line "cleanUrls": true in your file and save it. That's it.

After adding the line firebase.json will look like this :

{
  "hosting": {
    "public": "public",
    "cleanUrls": true, 
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}
Harsh Prajapati
  • 510
  • 5
  • 7
  • +1. I wonder if something similar could be done for GitHub pages, because it seems that the top-voted answers [don't work if you're hosting the site on GitHub Pages (gh-pages)](https://stackoverflow.com/q/13446435/1271772)? – Nike Aug 20 '22 at 19:17
15

Thanks for your replies. I have already solved my problem. Suppose I have my pages under http://www.yoursite.com/html, the following .htaccess rules apply.

<IfModule mod_rewrite.c>
   RewriteEngine On
   RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /html/(.*).html\ HTTP/
   RewriteRule .* http://localhost/html/%1 [R=301,L]

   RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /html/(.*)\ HTTP/
   RewriteRule .* %1.html [L]
</IfModule>
Rince Thomas
  • 4,158
  • 5
  • 25
  • 44
Dave
  • 3,328
  • 7
  • 26
  • 30
13

Good question, but it seems to have confused people. The answers are almost equally divided between those who thought Dave (the OP) was saving his HTML pages without the .html extension, and those who thought he was saving them as normal (with .html), but wanting the URL to show up without. While the question could have been worded a little better, I think it’s clear what he meant. If he was saving pages without .html, his two question (‘how to remove .html') and (how to ‘redirect any url with .html’) would be exactly the same question! So that interpretation doesn’t make much sense. Also, his first comment (about avoiding an infinite loop) and his own answer seem to confirm this.

So let’s start by rephrasing the question and breaking down the task. We want to accomplish two things:

  1. Visibly remove the .html if it’s part of the requested URL (e.g. /page.html)
  2. Point the cropped URL (e.g. /page) back to the actual file (/page.html).

There’s nothing difficult about doing either of these things. (We could achieve the second one simply by enabling MultiViews.) The challenge here is doing them both without creating an infinite loop.

Dave’s own answer got the job done, but it’s pretty convoluted and not at all portable. (Sorry Dave.) Łukasz Habrzyk seems to have cleaned up Anmol’s answer, and finally Amit Verma improved on them both. However, none of them explained how their solutions solved the fundamental problem—how to avoid an infinite loop. As I understand it, they work because THE_REQUEST variable holds the original request from the browser. As such, the condition (RewriteCond %{THE_REQUEST}) only gets triggered once. Since it doesn’t get triggered upon a rewrite, you avoid the infinite loop scenario. But then you're dealing with the full HTTP request—GET, HTTP and all—which partly explains some of the uglier regex examples on this page.

I’m going to offer one more approach, which I think is easier to understand. I hope this helps future readers understand the code they’re using, rather than just copying and pasting code they barely understand and hoping for the best.

RewriteEngine on

# Remove .html (or htm) from visible URL (permanent redirect)
RewriteCond %{REQUEST_URI} ^/(.+)\.html?$ [nocase]
RewriteRule ^ /%1 [L,R=301]

# Quietly point back to the HTML file (temporary/undefined redirect):
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^ %{REQUEST_URI}.html [END]

Let’s break it down…

The first rule is pretty simple. The condition matches any URL ending in .html (or .htm) and redirects to the URL without the filename extension. It's a permanent redirect to indicate that the cropped URL is the canonical one.

The second rule is simple too. The first condition will only pass if the requested filename is not a valid directory (!-d). The second will only pass if the filename refers to a valid file (-f) with the .html extension added. If both conditions pass, the rewrite rule simply adds ‘.html’ to the filename. And then the magic happens… [END]. Yep, that’s all it takes to prevent an infinite loop. The Apache RewriteRule Flags documentation explains it:

Using the [END] flag terminates not only the current round of rewrite processing (like [L]) but also prevents any subsequent rewrite processing from occurring in per-directory (htaccess) context.

Kal
  • 2,098
  • 24
  • 23
10

Resorting to using .htaccess to rewrite the URLs for static HTML is generally not only unnecessary, but also bad for you website's performance. Enabling .htaccess is also an unnecessary security vulnerability - turning it off eliminates a significant number of potential issues. The same rules for each .htaccess file can instead go in a <Directory> section for that directory, and it will be more performant if you then set AllowOverride None because it won't need to check each directory for a .htaccess file, and more secure because an attacker can't change the vhost config without root access.

If you don't need .htaccess in a VPS environment, you can disable it entirely and get better performance from your web server.

All you need to do is move your individual files from a structure like this:

index.html
about.html
products.html
terms.html

To a structure like this:

index.html
about/index.html
products/index.html
terms/index.html

Your web server will then render the appropriate pages - if you load /about/, it will treat that as /about/index.html.

This won't rewrite the URL if anyone visits the old one, though, so it would need redirects to be in place if it was retroactively applied to an existing site.

Matthew Daly
  • 9,212
  • 2
  • 42
  • 83
  • If you're managing the VPS, why wouldn't you add the rewrites to the Apache configuration files (httpd.conf), rather than .htaccess? If you're not an admin, sure… you'll get a small performance hit. I guess you'd need to weigh this up against the impracticability of creating a directory for every file on your website. – Kal Sep 09 '20 at 05:37
  • @Kal Actually I'd put the rewrites in the per-site vhost config, not https.conf. They'd then be basically the same as in the .htaccess. – Matthew Daly Sep 09 '20 at 07:21
  • I mean httpd.conf – Matthew Daly Sep 09 '20 at 07:34
  • And you wouldn't need a directory for every file on your website - at most it'd be a case of moving all your rules into `` directives in your vhost config. – Matthew Daly Sep 09 '20 at 08:11
  • To clarify, I wouldn't modify the main httpd.conf file either. I use DirectAdmin, where each user has their own httpd.conf file. The point is, configuring your VirtualHost entries (if you can) mitigates the performance issues you raised. It sounds like we agree on that. – Kal Sep 10 '20 at 00:30
  • I think you misunderstood my comment about directories. I was just saying, if you don't have access to httpd config, you'd have to weigh up the performance overhead of .htaccess against your recommended solution, which was to create a directory for every file and rename each one ‘index.html’. This is a common technique (one that I've selectively used myself I might add), but pretty impractical for anything but a very small site. – Kal Sep 10 '20 at 01:17
  • That's a really good point and a clever solution. – CristianMoisei Mar 23 '21 at 19:08
  • is it normal that I still see ".html" extension locally in the url? Will it be gone on the server? – Farid Mammadaliyev Jan 31 '23 at 23:34
  • 1
    @FaridMammadaliyev As long you're running it using a web server rather than just navigating the files in a local folder, then yes - this is part of how a web server serves up the files, with it defaulting to `index.html`. Linking to or loading `/about/index.html` will work, but so will `/about/` and so your internal links will need to point at the folder, not the file – Matthew Daly Feb 01 '23 at 11:16
8

I use this .htacess for removing .html extantion from my url site, please verify this is correct code:

    RewriteEngine on
RewriteBase /
RewriteCond %{http://www.proofers.co.uk/new} !(\.[^./]+)$
RewriteCond %{REQUEST_fileNAME} !-d
RewriteCond %{REQUEST_fileNAME} !-f
RewriteRule (.*) /$1.html [L]
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /([^.]+)\.html\ HTTP
RewriteRule ^([^.]+)\.html$ http://www.proofers.co.uk/new/$1 [R=301,L]
Anmol
  • 81
  • 1
  • 1
  • This seemed to work for me, unlike the other solutions presented here, thank you. I would add though that you still need to update the link(s) in your HTML though (so if you originally are linking to your .html file as , you should update it as and then it will work.) – Lorenzo Mar 01 '14 at 20:17
  • The core change for me was the `RewriteBase /` bit. Unfortunately I don't understand why it worked, but I guess I will learn soon. – Keno Feb 21 '17 at 01:52
3

Making my own contribution to this question by improving the answer from @amit-verma (https://stackoverflow.com/a/34726322/2837434) :

In my case I had an issue where RewriteCond %{REQUEST_FILENAME}.html -f was triggering (believing the file existed) even when I was not expecting it :

%{REQUEST_FILENAME}.html was giving me /var/www/example.com/page.html for all these cases :

  • www.example.com/page (expected)
  • www.example.com/page/ (also quite expected)
  • www.example.com/page/subpage (not expected)

So the file it was trying to load (believing if was /var/www/example.com/page.html) were :

  • www.example.com/page => /var/www/example/page.html (ok)
  • www.example.com/page/ => /var/www/example/page/.html (not ok)
  • www.example.com/page/subpage => /var/www/example/page/subpage.html (not ok)

Only the first one is actually pointing to an existing file, other requests were giving me 500 errors as it kept believing the file existed and appending .html repeatedly.

The solution for me was to replace RewriteCond %{REQUEST_FILENAME}.html -f with RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f

Here is my entire .htaccess (I also added a rule to redirect the user from /index to /) :

# Redirect "/page.html" to "/page" (only if "/pages.html" exists)
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{THE_REQUEST} /(.+)\.html [NC]
RewriteRule ^(.+)\.html$ /$1 [NC,R=301,L]

# redirect "/index" to "/"
RewriteRule ^index$ / [NC,R=301,L]

# Load "/page.html" when requesting "/page" (only if "/pages.html" exists)
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule ^ /%{REQUEST_URI}.html [QSA,L]

Here is a result example to help you understand all the cases :

Considering I have only 2 html files on my server (index.html & page.html)

  • www.example.com/index.html => redirects to www.example.com
  • www.example.com/index => redirects to www.example.com
  • www.example.com => renders /var/www/example.com/index.html
  • www.example.com/page.html => redirects to www.example.com/page
  • www.example.com/page => renders /var/www/example.com/page.html
  • www.example.com/page/subpage => returns 404 not found
  • www.example.com/index.html/ => returns 404 not found
  • www.example.com/page.html/ => returns 404 not found
  • www.example.com/test.html => returns 404 not found

No more 500 errors


Also, just to help you debug your redirections, consider disabling the network cache in your browser (as old 301 redirections my be in cache, wich may cause some headaches ):

Screenshot of Google Chrome's console, showing how to disable the "network cache"

Typhon
  • 946
  • 1
  • 15
  • 23
  • Thank you for Brief explanations.. One fix, RewriteRule ^index$ / [NC,R=301,L] to RewriteRule ^index.*$ / [NC,R=301,L] to avoid index.php to loaded. So now index or index.php or index.html will be redirect to homepage – Ajmal PraveeN Aug 30 '21 at 17:03
2

first create a .htaccess file and set contents to -

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html

next remove .html from all your files eg. test.html became just test and also if you wanna open a file from another file then also remove .html from it and just file name

Aarush Kumar
  • 149
  • 1
  • 8
1

Use a hash tag.

May not be exactly what you want but it solves the problem of removing the extension.

Say you have a html page saved as about.html and you don't want that pesky extension you could use a hash tag and redirect to the correct page.

switch(window.location.hash.substring(1)){
      case 'about':
      window.location = 'about.html';
      break;
  }

Routing to yoursite.com#about will take you to yoursite.com/about.html. I used this to make my links cleaner.

LeanKhan
  • 123
  • 1
  • 11
1

If you have a small static website and HTML files are in the root directory.

Open every HTML file and make the next changes:

  1. Replace href="index.html" with href="/".
  2. Remove .html in all local links. For example: "href="about.html"" should look like "href="about"".
Bandyliuk
  • 489
  • 4
  • 13
0

To remove the .html extension from your URLs, you can use the following code in root/htaccess :

#mode_rerwrite start here

RewriteEngine On

# does not apply to existing directores, meaning that if the folder exists on server then don't change anything and don't run the rule.

RewriteCond %{REQUEST_FILENAME} !-d

#Check for file in directory with .html extension 

RewriteCond %{REQUEST_FILENAME}\.html !-f

#Here we actually show the page that has .html extension

RewriteRule ^(.*)$ $1.html [NC,L]

Thanks

Akash Limbani
  • 1,283
  • 4
  • 14
  • 34
  • "Check for file in directory with .html extension" - But the condition is checking that the file does NOT exist! You need to remove the `!` prefix on the _CondPattern_. – MrWhite Oct 12 '21 at 16:13
0

For this, you have to rewrite the URL from /page.html to /page You can easily implement this on any extension like .html .php etc

RewriteRule ^(.*).html$ $1.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.html [NC,L]

You will get a URL something like this: example.com/page.html to example.com/page Please note both URLs below will be accessible

example.com/page.html and example.com/page If you don't want to show page.html Try this

RewriteRule ^(.*).html$ $1 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.html [NC,L]

More info here

0
     RewriteEngine On
     RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /html/(.*).html\ HTTP/
     RewriteRule .* https://example.com/html/%1 [R=301,L]
     RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /html/(.*)\ HTTP/
     RewriteRule .* %1.html [L]

it might work because its working in my case

AD_
  • 51
  • 5
-4
RewriteRule /(.+)(\.html)$ /$1 [R=301,L] 

Try this :) don't know if it works.

Jarsäter
  • 338
  • 3
  • 9
  • 16
    You shouldn't post it as an answer if you're unsure if it works or not. –  Jun 22 '19 at 17:41