Guide to 404 Pages with ExpressionEngine

Posted on 16 February 2011 | by Ryan Battles

Guide to 404 Pages with ExpressionEngine

According to a recent study, 7% of traffic to any given website will result in a 404 error.  By default, most ExpressionEngine sites will just redirect the user to the homepage, without any indication of what has just happened.  This guide explains all you need to know about setting up your system for optimal 404 pages.

A 404 Error, which simply means “Page not Found”, is sent from the server whenever a user types in an incorrect address for a particular site.  This is not to be confused with a “Server not found” error, which occurs when the website as a whole cannot be reached (or doesn’t exist).  In order to be effective, a 404 error page must do the following:

  • Reassure visitors that they reached the correct site
  • Explain to the users why they are there
  • Help them find what they are looking for

In ExpressionEngine, if someone types in an incorrect page address, by default they are sent to the homepage.  If they have typed in a proper template group name, but an invalid URL segment, they may see a working page and not know that they typed in an invalid URL.

For example, I have a template group on this site called “about”.  Within the “about” template group, you are able to access information about various services that I offer, such as “ExpressionEngine Development”.  This page is located at http://joviawebstudio.com/about/expressionengine_development/.  Before I modified my setup, someone could type in http://joviawebstudio.com/about/toilet_cleaning/, and a page would have come up, most likely the last service that I published into the system.  The user would have no idea that their URL was not what I intended that page to be.  This is not helpful for their experience, or my search engine rankings :)

Create a 404 Page & Set Your Template Preferences

If you haven’t already, you need to create a 404 error page template somewhere in your system.  It doesn’t have to be anything fancy just yet, simply a template that will be designated for 404 error messages.  In my examples, I am using a template titled “404” in the “pages” template group.

In the control panel > template manager, click on the tab for “Global Template Preferences”, you need to set the “Enable Strict URLs” setting to “yes”, and set your template for your 404 error page.

404 Case 1: Mistyped URLs

The first thing you need to do is have a system for setting up your site that is always mindful of mistyped URLs.  For every page that is a single entry page (has a limit=“1”), like a blog post page or services page, make sure that your channel entries loop contains the “require_entry=‘yes’” parameter:

{exp:channel:entries channel="blog" limit="1" require_entry="yes"}

    
... {!--entire page content goes here--}

{
/exp:channel:entries} 

This will only give a result if a specific entry url title is found within the segments of the url.  Once you have that set up, you need to have an error handler in there to catch times where there is no entry for that url title.  This can be done with the “if no_results” conditional, and using ExpressionEngine’s special “{redirect=“404”}” tag.  This will automatically embed the 404 template that you designated in template preferences.

{exp:channel:entries channel="blog" limit="1" require_entry="yes"}

    {if no_results}

        {redirect
="404"}

    {
/if}

    
... {!--entire page content goes here--}

{
/exp:channel:entries} 

For the above example, the visitor would see the URL that they typed in the address bar, but the page’s content would be that of your 404 error page.  Another method would be to enable PHP in the template (parsed upon output) and force a 301 redirect to the actual 404 page, like so:

{exp:channel:entries channel="blog" limit="1" require_entry="yes"}

    {if no_results}

        <?
        Header
"HTTP/1.1 301 Moved Permanently" );
        
Header"Location: /pages/404" );
        die();
        
?>

    {
/if}

    
... {!--entire page content goes here--}

{
/exp:channel:entries} 

Whichever method you choose is up to you.  Personally, in this case I believe that the former is the better choice, as it allows the user to see what URL they have typed incorrectly, and technically, the page isn’t found, it wasn’t once there and then moved, which is the technical case for a 301 redirect.

404 Case 2: Additional URL Segments

Note: credit for the addition of this statement goes to Darren Vlacich who pointed out via twitter (@CapeLinks) that this section was a hole in my initial writeup. Thanks Darren!

ExpressionEngine is designed to be flexible with additional URLs.  For example, without modification, I would be able to access this page at both:

http://joviawebstudio.com/blog/guide_to_404_pages_with_expressionengine/

and

http://joviawebstudio.com/blog/guide_to_404_pages_with_expressionengine/EXTRA/SEGMENTS/WORK/TOO/

Now, in order to prevent that from happening, you can add another conditional with your if no_results loop mentioned above.  Because I do not need any URL segments past segment 2, I will check for a segment 3 and give a 404:

{exp:channel:entries channel="blog" limit="1" require_entry="yes"}

    {if no_results 
OR segment_3!=""}

        {redirect
="404"}

    {
/if}

    
... {!--entire page content goes here--}

{
/exp:channel:entries} 

As mentioned earlier, you could also use some PHP to force a 301 redirect.  However, instead of redirecting to the 404 page, you can simply force a redirect that removes the URL segments.  To do that, you must again enable PHP in template (parsed on output), then use this code pattern:

{if segment_3!=""}

    <?
    Header
"HTTP/1.1 301 Moved Permanently" );
    
Header"Location: /{segment_1}/{segment_2}" );
    die();
    
?>
    
{
/if}

{exp
:channel:entries channel="blog" limit="1" require_entry="yes"}

    {if no_results}

        {redirect
="404"}

    {
/if}

    
... {!--entire page content goes here--}

{
/exp:channel:entries} 

While the above code is still not technically correct, as the page is still supposed to be “not found” instead of “moved”, we are practicing a little bit of accessibility and providing meaningful content for those who have unnecessary URL segments.  Again, which method you choose is up to you, both are acceptable.

No matter which method you choose, just about every page in your EE arsenal needs to receive a similar type of treatment, otherwise people could technically bypass the 404 page by adding an extra URL segment.  You may not care about this, but someone could technically link to your site with a nefarious extra URL segment, and it would start showing up in Google search results.  So, every page needs something of this sort (change the segment number to whatever segment is no longer necessary):

{if segment_3!=""}

    {redirect
="404"}

{
/if}

... {!-- entire page content goes here--

OR

{if segment_3!=""}

    <?
    Header
"HTTP/1.1 301 Moved Permanently" );
    
Header"Location: /pages/404" );
    die();
    
?>

{
/if}

... {!-- entire page content goes here--

404 Case 3: URLs Outside of ExpressionEngine

For pages that are rendered on your website, but are outside of your ExpressionEngine installation, you want the 404 page to work for those too.  This has to be done by modifying your .htaccess file with the following line of code:

ErrorDocument 404 /index.php/pages/404 

Make sure to change the “index.php/pages/404” path above to whatever you use for your system.

For the case where images, flash, and other misc files are not found, a simple 404 message should be returned, so you’ll also want to add this line to your .htaccess file:

<FilesMatch "(\.jpe?g|gif|png|bmp|css|js|flv)$">
  
ErrorDocument 404 "File Not Found"
</FilesMatch

You can read more about ExpressionEngine .htaccess modifications in an article on Devot-ee.com: Simple .htaccess for ExpressionEngine Sites, or you may prefer to install Leevi Graham’s excellent .htaccess generator for EE1 or EE2.

Craft an Effective 404 Page Template

Now that I am to this point, I realize it may have been more helpful to start with this, but I figured that many people reading this page aren’t as interested in the content of the 404 page as the actual coding nerdery that goes on behind it.

Basically, any effective 404 page will do the following:

  • Reflect the branding of the parent site
  • Have helpful information
  • Have some personality

If that statistic is true about 7% of Internet page queries landing on 404 pages, you should give yours a litlte personality.  Here are some examples of creative 404 error pages:

Because your 404 page will be in ExpressionEngine, you will have the full templating engine at your disposal.  Feel free to load in a dynamic sitemap, or links to your most popular pages based off of tracking views.

Harden Up your Soft 404

Here’s a little-known gotcha, because we’ve created a 404 page within ExpressionEngine, in some cases, the page technically is not a “page not found”, it is found.  For example, with the technique of requiring entries and embedding the 404 page if no results are found, the server sends out a status 200, or “OK” instead of status 404, “page not found”.  This normally doesn’t matter to the end user, but to search engines and other robots, it means quite a bit.

The solution is simple here.  You must enable PHP for your 404 template, then place the following code at the top of your 404 page:

<?php
    header
("HTTP/1.0 404 Not Found");
?> 

This changes your header that the server sends out with your document to a 404 status instead of a 200 status.  In order for this to work, you also cannot have caching enabled for your 404 page.  If you do have caching enabled, the cached version will be served with a status 200 no matter what header you put in your template via PHP.

Note for EE2: Erik Reagan from Focus Lab, LLC pointed out in the comments that for EE2, utilizing a CodeIgniter function to set the header with the Output class is a better option:

$this->EE->output->set_header("HTTP/1.0 404 Not Found"); 

Bonus Treat: Rob Sanchez from Barrett Newton Interactive created a nice little plugin that will do the trick with some EE code:

{exp:http_header status="404"

Grab the http_header plugin here. (EE 2 Only).

It should be noted that you can’t actually see your header status in the browser, so an online tool is helpful here: Check Web Page Status Codes

Use Analytics and 301 Redirects

Because your 404 page is under your control, you can also use analytics to find out why the heck people got there in the first place.  If you use Google Analytics, just place your tracking code on the 404 page, and use their tools after a while to find out why people are getting there.

If you find that many errors are being caused by a common URL, for example, if everybody keeps typing in “/blogs/” instead of “/blog/”, or there is a link to your site somewhere out there that you cannot control, but know exactly what it is, you can use your .htaccess file to create a 301 redirect.

Here is an example 301 redirect that I use on my site to make a shorter url for my EE development page:

RewriteEngine On

RewriteBase 
/

redirect 301 /ee /about/expressionengine_development 

Conclusion

Implementing the above steps will both create a better experience with your site visitors, and also improve your search engine rankings.  Another tool that helps out greatly with 404 errors is Google Webmaster Tools.  There is a tab specifically designated for 404 errors, and will provide a nice list of links within your site that point to incorrect URLs.  Happy 404’ing!

P.S.  If you have a custom 404 page that you’d like to share, please leave a link to it in the comments.

 

Tags: expressionengine, seo, web development, htaccess, 404

Related Posts

Comments (38)

Erik Reagan
Feb. 18, 2011

Hey Ryan,

Nice explination on using 404's with EE.

I may have done something wrong in our templates but we could never get the 404 status header set in EE2 templates with PHP turned on (per your example). It worked fine for us in EE1 though.

So what we ended up doing was just using the CodeIgniter function to set the header with the Output class. It looks like this for us:

$this->EE->output->set_header("HTTP/1.0 404 Not Found"); 


You can see our quirky 404 page here.

Ryan Battles
Feb. 18, 2011

Erik,

Thanks for the tip for EE2, I'll post it as a note higher up in the article.

Great 404 page too!

Billy
Feb. 19, 2011

Thanks for the tip.

Curious to know if there was a sudden spike of 404's on your site?.. :) (i admit, i forced one)

Billy

Bjørn Børresen
Feb. 19, 2011

Good writeup! :)

I'm curious if anyone has a good site-wide implementation of 404's in EE though .. e.g.

http://joviawebstudio.com/blog/wieojwejiowejeiwjweiowejiowjwejiiwejweioj/

That will show your latest blogpost, it should be a 404, no?

EllisLab hasn't fixed this themselves on their site, e.g. this will be a valid link to a blogpost:

http://expressionengine.com/blog/entry/new_price_expressionengine_is_now_1_dollar_only

I would think this is what require_entry="yes" should fix?

Bjørn Børresen
Feb. 19, 2011

.. and yeah, agreed, Erik's 404 page was pretty funny :-)

Erik Reagan
Feb. 19, 2011

Our 404 is pretty site-wide with a few exceptions.

Ryan Battles
Feb. 19, 2011

I think an url_title="{segment_2}" ought to fix that.

Erik Reagan
Feb. 19, 2011

Ryan

Someone could still lump on a rogue segment after your dynamic segment - technically :p

Bjørn Børresen
Feb. 19, 2011

Erik, found one of the exceptions, for instance this hidden document:

http://focuslabllc.com/member/how-we-plan-to-take-over-the-world

Apparantly it can only be read by "logged-in users with proper access privileges"

:-p

100% sitewide is pretty difficult, it seems, esp. if you bring the members templates / forums / wiki / etc. into the mix.

Erik Reagan
Feb. 19, 2011

Bjørn,

My first thought upon seeing your created URL was that it actually shouldn't work. After all, we change the member trigger word for all of our sites.

Low and behold that we had a typo in our config setting that phrase. :p

So - that's fixed and it's one less non-404 page in action :)

Cheers

Ryan Battles
Feb. 19, 2011

Looks like i posted this a little prematurely. I will do some more extensive testing and republish this afternoon.

Rob Sanchez
Feb. 19, 2011

I just put up an http_header plugin on github: https://github.com/rsanchez/http_header

So for Harden Up your Soft 404 you can do:
{exp:http_header status="404"}

Ryan Battles
Feb. 19, 2011

Way to go Rob! I'm going to have to take Erik's Train-ee 2 course so I can learn to whip stuff like that up too. What a great addition.

Robert
Feb. 19, 2011

I have template_group called 404. Template's page is:

{exp:channel:entries channel="pages" limit="1" require_entry="yes"}

{if no_results}

{redirect="404/index"} {/if}
[content]
{/exp:channel:entries}

it's simpler. I think.

Ryan Battles
Feb. 19, 2011

Pfew, just reworked this entire post to include a few more notes that I've gained throughout the morning. Thank you everyone in the comments that have made additions. I've tried to give credit where credit is due.

This topic sure does have quite a bit of complexity to it. Much more than I thought it would when I first started writing this post!

If you continue to find holes in it, keep adding to the comments. It is often through comments on other blog posts that I find the solution I am looking for.

Sean
Feb. 19, 2011

Thanks for this great, informative article. Every time I do a 404, I've done it differently. I think with this article in hand, I'm going to be able to standardize my approach to this.

John Faulds
Feb. 21, 2011

Seems to me you could also use Rob's plugin with it's location parameter to do a 301 redirect without the need to turn PHP on in the template.

Bjørn Børresen
Feb. 21, 2011

Ryan,

thanks for taking the time to investigate this topic.

Turned out to be very informative indeed :)

Ryan Battles
Feb. 21, 2011

John,

Yes, I agree that Rob's plugin is definitely the easiest way to go. I'm not sure, but I think he whipped it up just yesterday!

After writing the article, I realized that all of the PHP stuff could be done within his plugin. Very helpful guy that Rob.

I figured I'd leave the original method in there for people who are addon purists and try to do things natively, but I need to go back through the article and pimp that addon some more because of all it can do.

Dominic
Feb. 22, 2011

Great post. I've spent a good while arriving at many of the same conclusions you have.

However, instead of embedding a 404:

{embed="pages/404"}

I'm using a redirect:

{redirect="404"}

Is there any benefit using an embed? The URL doesn't change for me...

Also, to 404 missing assets I've added the following to my .htaccess file:

# Simple 404 for missing files
<FilesMatch "(\.jpe?g|gif|png|bmp|css|js|flv)$">
ErrorDocument 404 /404/index
</FilesMatch>

Ryan Battles
Feb. 22, 2011

Dominic, I just noticed the redirect="404" statement in the EE documentation. Thanks for pointing that out. I've gone through the article and replaced the embed="pages/404" statements with redirect="404".

Also, good point about 404ing the missing assets. I use that too, but only because it comes with Leevi Graham's .htaccess Generator extension by default. Would they not be covered by the ErrorDocument 404 statement otherwise put into the .htaccess file?

Dominic
Feb. 22, 2011

Cool for changing the embed to a redirect.

ErrorDocument 404 /index.php/pages/404 alone is not sufficient to catch missing assets, which is only a problem if you aren't using the .htaccess generator like you say. Maybe worth a quick mention in the guide?

Great reference for 404's though, I'll be turning to this all the time.

Ryan Battles
Feb. 22, 2011

Dominic, just added the reference to a simple 404 for missing files. After doing some additional research, it looks like if I write in a simple 404 message instead of a link for those pages, then the full 404 page won't be called upon a rogue reference:

<FilesMatch "(\.jpe?g|gif|png|bmp|css|js|flv)$">
  
ErrorDocument 404 "File Not Found"
</FilesMatch

Lee
Feb. 23, 2011

What about 404 for url's with a segment(s) removed, what's the right way to 404 these? For example:

real url: site.com/tg/t/category/blur
site.com/tg/t/category
site.com/tg/t

Ryan Battles
Feb. 23, 2011

That's probably going to vary from situation to situation, and one answer will not fit all. You really could go a number of routes I'd imagine, from creating a useful template for a category landing page, redirecting somewhere else useful (not a 404), or just adding some verbage to your 404 that they might not have a complete URL and offer some suggestions. You could use some conditionals in your 404 to look at their segment values and offer some possible solutions to live links. For example:

{if segment_3=="category"}
It looks like you are trying to find a category
, try one of these:
[list of categories]
{
/if} 

Boyink
Feb. 24, 2011

The only thing I'd add to this is (at least as far as I can tell) there is no way to validate category url titles on templates loaded at category urls:

http://yoursite.com/products/category/puppy - where puppy is valid but then

http://yoursite.com/products/category/kitten - where kitten is not valid.

I've played around and found approaches that worked for entries that were only assigned to one category but failed when entries were assigned to >1 category.

I logged a FR but it's been quite a while ago...

Erik Reagan
Feb. 24, 2011

Mike,

I often use Low Seg2Cat in projects and using that allows for a conditional such as "if I'm on a category page and Low Seg2Cat doesn't find the category...

{if segment_2 == 'category' && '{segment_3_category_name}' == ''}
...something...
{/if} 


Where {segment_3_cateogory_name} is a Low Seg2Cat global variable defined at the beginning of a page load. If the category url doesn't match any categories then it isn't set.

Boyink
Feb. 24, 2011

That's cool. I'd still like to see a 1st party/native way of handling it but good to have an alternative in the meantime..;)

Erik Reagan
Feb. 24, 2011


I'd still like to see a 1st party/native way of handling it

Ahh, the age-old [insert-software-name-here] statement.
:)

Lee
Feb. 24, 2011

For http://yoursite.com/products/category/kitten

Could you have a channel tag on the page with limit 1, entry required and use the redirect in an if no results tag pair?

Boyink
Feb. 24, 2011

require_entry is only valid on single entry pages with either a url_title or entry_id showing in the URL.

Kristen Grote
Oct. 11, 2011

Hi Ryan!

Fantastic article, thanks for being so thorough!

I've been running into some issues while implementing this and apparently it's partly because the {if no_results} conditional is only meant to be a "simple" conditional. Meaning instead of:

{if no_results}
{redirect="404"}
{if:else}
[content]
{/if}

It should just be:

{if no_results}
{redirect="404"}
{/if}
[content]

That's what I've heard from 2 separate EE support reps, anyway.

Ryan Battles
Oct. 11, 2011

@Kristen - Great catch. I have learned more about the way redirects work with PHP and ExpressionEngine since writing this article, and you are correct, the simple conditional is all that you need. I have reworked the code snippets to reflect a simple conditional. Thanks for pointing that out!

Tyler Herman
Oct. 19, 2011

Thanks for putting this together. This should be part of the EE User Guide. Easy to understand and implement.

david blake
Oct. 21, 2011

Very helpful article
http://dreamsoumik.blogspot.com

Daniel Harris
Oct. 31, 2011

What about member templates? Not getting a 404 when typing /member/register/whatever - but rather get an Invalid Page Request error. How would you handle these templates?

Ryan Battles
Oct. 31, 2011

Ahh, excellent catch Daniel. The member profile trigger word (default: /member/) lies outside of the templates altogether, so all of the above techniques don't apply. To be honest, I'm not sure how to best handle this if it doesn't throw a 404 out of the box. Perhaps an extension is needed or even a core hack to add in a 404 error for bad member urls.

Personally, if I need to open up member management to the users of the site outside of the control panel, I use Solspace's User addon to use the templates for displaying the appropriate controls. This allows me to implement the 404 methods outlined above. As for the member trigger word, I usually change it to a 16 character alpha-numeric word that is extremely hard to guess. I'm not sure if there is another way at this point.

Erik Reagan
Oct. 31, 2011

I'm with Ryan on the member stuff. If we ever use front-end member templates it's by way of a 3rd party add-on such as User or Zoo Visitor.

I can actually say that we've never used the native EE member templates. Ever.

Join the Discussion


Name: (required)
Email: (not published)

Notify me of follow-up comments

Please enter the word you see in the image below:


Submit
Comment: