Simpler URLs with a Single Template
by Tony Geer
One of the greatest strengths of ExpressionEngine is its flexibility, so it’s no surprise that there are usually multiple ways of accomplishing the same task. But as you continue to work more and more with EE, you sometimes find better ways of doing things, whether it’s getting something done more efficiently or with less code (or hey, maybe both!).
One of the areas that I’ve always thought could be improved was the way templates are utilized to display content. I want to use a single template to display both the listing of entries and the individual entry. To explain what I mean, let’s look at the following URL (from the Agile Records theme demos site that you can install) after I’ve removed index.php:
http://domain.com/news/comments/welcome_to_the_example_site
After the domain bit, this URL consists of three parts
segment_1: news (a template group)segment_2: comments (a template)segment_3: welcome_to_the_example_site (the url_title of the entry)
As you can tell, it’s using the comments template (which is inside of the news template group) to display single entries. Splitting it up into three parts like this was a good way for EllisLab to keep things simple and explain how template groups and templates work, but it has resulted in an entirely unecessary segment being added - “comments”. I think this is inefficient because:
- This extra segment makes the URL longer and therefore harder to remember.
- It’s always a good idea to try as much as possible to have URLs that represent different sections of the site so that if you remove the last segment of a URL you go back up a branch in the tree and view the parent information. However, with this structure if you remove the
url_titleof an entry and go to http://domain.com/news/comments you get thrown a 404 error. So in terms of usability it’s not that great. - In terms of search engine optimization, Matt Cutts of Google says it’s not necessarily a big deal to have that extra segment in there, but I found that saying it wasn’t a “major factor” to be a bit ambiguous, so I’d rather be on the safe side and keep the number of URL segments as low as possible, if it’s at all practical.
So here’s what we’re going to do: we’re going to eliminate the use of the comments template by using our index template to display an individual entry if a url_title is passed to it, while continuing to have it display a list of entries if it’s called with no extra segment at the end. Here’s how that’s done:
{if segment_2==""}
{exp:channel:entries
channel="news"
disable="categories|category_fields|member_data"
limit="10"
dynamic="no"
paginate="bottom"
}
<h1><a href="{site_url}news/{url_title}">{title}</a></h1>
{news_body}
{paginate}
<p>Page {current_page} of {total_pages} pages {pagination_links}</p>
{/paginate}
{/exp:channel:entries}
{if:else}
{exp:channel:entries
channel="news"
disable="categories|category_fields|member_data|pagination"
limit="1"
url_title="{segment_2}"
}
{if no_results}
<p>No entry found.</p>
{/if}
<h1>{title}</h1>
{news_body}
{/exp:channel:entries}
{/if}
The first part checks to see if segment_2 exists, and if it doesn’t exist, it displays the ten most recent entries from our news channel. At this point our URL would be http://domain.com/news. It’s important that we set dynamic to no so that it doesn’t try to look in our URL for any indication of which entries we want to display, and as always, we disable anything that we’re not going to use. It then creates a link for each of our entries using our site_url variable, our template group and the url_title of that particular entry.
The second part of our conditional kicks in if segment_2 exists, i.e. when someone clicks on the link to one of the entries on our index template. An example of our URL at this point would be http://domain.com/news/welcome_to_the_example_site. It tries to pull an entry whose url_title matches segment_2 and displays it. If no url_title matches our segment, we make use of the if no results conditional to display an error message.
And there, we’ve done it! A cleaner, more memorable URL that has an excellent hierarchy. However, there are two small issues with this:
- Pagination won’t work, because once the pagination string is added to the URL it becomes
segment_2and the second part of the conditional assumes thatsegment_2is always theurl_titleof an entry, so theif no_resultsconditional kicks in. - Depending on how much work you’re doing in each conditional, a complex template can quickly become quite resource intensive as EE actually parses all of the conditions in a template then checks to see which one matches your criteria and discards the others.
The good news we can solve both of these issues with one little add-on.
Taking it Further - Adding Switchee to the mix
Switchee is a great little plugin that allows you to use case conditionals instead of “if:else” conditionals to speed up your templates. It does this by only evaluating code in the conditional that matches your criteria, so everything is no longer parsed. Here’s our template code modified to use Switchee’s syntax:
{exp:switchee variable="{segment_2}" parse="inward"}
{case value="#^P(\d+)$#|''"}
{exp:channel:entries
channel="news"
disable="categories|category_fields|member_data"
limit="10"
dynamic="no"
paginate="bottom"
}
<h1><a href="{site_url}news/{url_title}">{title}</a></h1>
{news_body}
{paginate}
<p>Page {current_page} of {total_pages} pages {pagination_links}</p>
{/paginate}
{/exp:channel:entries}
{/case}
{case default="Yes"}
{exp:channel:entries
channel="news"
disable="categories|category_fields|member_data|pagination"
limit="1"
url_title="{segment_2}"
}
{if no_results}
<p>No entry found.</p>
{/if}
<h1>{title}</h1>
{news_body}
{/exp:channel:entries}
{/case}
{/exp:switchee}
You’ll see that in addition to checking for an empty segment_2 (’ ‘), our first case now also uses a regular expression to check segment_2 for a pagination string ("#^P(\d+)$#"). So now if you load the second page of a list of entries when accessing the index template and you have a pagination string in segment_2, e.g. P1, pagination will work nicely!
And as I mentioned before, because we’re using Switchee, whenever we load a URL it will check to see which case matches the criteria we’re looking for and only parse that case, so our template will be faster. When I was using the “if:else” conditional (without pagination and only two entries) the page used 28 queries to pull all the information that I required. When I used Switchee this dropped to 24, a pretty significant decrease, even though this is a very simple template.
So there we go: we’ve gotten simpler URLs and we’ve done it more efficiently and with less code than before!
Post to Twitter
Bryant — 09:29 on 08.25.2011
Great tip. I’ve definitely used this approach on a few projects, but it’s always great to see methods confirmed by others. Also, great to see the use of switchee. I would actually consider the standard conditional method to be a bad practice as it can dramatically slow down your page, as you mentioned.
Sean — 09:45 on 08.25.2011
I’ve been using switchee to do this for a little while, but can’t seem to get it to work with categories and pagination - any ideas on that?
Great post and introduction to this idea.
Tony Geer — 10:08 on 08.25.2011
Thanks guys!
Sean: I’ve never actually tried to do that, sorry :(
Jez Swinscoe — 13:28 on 08.25.2011
I’ve never found a satisfactory way of doing this and keeping EE’s native Pagination.
Maybe you could fake the pagination with offset=”{last_segment}” and add the pagination value in the last segment of the URL and do it that way.
Dominic — 21:54 on 08.25.2011
Nice tip. I might give that a try. I’m currently using if:else plus is:page plugin for much the same effect. Pagination is always the issue.
I’m more amazed that some many people find what Matt Cutts says about URLs as ‘ambiguous’. But that’s SEO’s for you…
From the video:
‘Virtually no difference what so ever’
‘doesn’t change how much page rank it gets’
‘wouldn’t worry very much at all’ (twice)
‘great thing for usability’
‘definitely recommend having your site categorised’
‘not a major factor’
And yet people seem to be unclear of his message because he said the word ‘major’ once in two minutes of footage.
Get a grip!
GDmac — 07:29 on 08.28.2011
Can’t entry pagination also use Nxx (when dynamic=no)?
http://expressionengine.com/user_guide/modules/comment/entries.html#par_dynamic
And also, what about comment pagination?
Tony Geer — 08:59 on 08.28.2011
Hi GD. When dynamic is set to no Pxx is still being used for the pagination. But even if Nxx were used instead, we’d still run into the issue of the conditionals thinking that Nxx is a url_title of an entry, so then we’d have to adjust our Pxx regular expression to use Nxx instead.
Dominic: I thought it was ambiguous because he does say all the things you highlight, but in the end (IMO) he slightly contradicts himself by saying it isn’t a “major factor.” We’re all free to interpret this however we like though
GDmac — 09:29 on 08.28.2011
Tony, that’s what the regular expression in switchee is for “#^P(\d+)$#|’’”
e.g. starts with P plus digit(s) OR empty.
Does the no_results return a 200 status code? (redirect=404)
Tony Geer — 09:35 on 08.28.2011
GD: I know that’s what the regular expression does
All I’m saying is if Nxx were used instead of Pxx, we’d still be in the same boat having to use regular expressions to prevent the second conditional kicking in and thinking it’s a url_title.
Here’s a pretty thorough examination of 404’s: http://joviawebstudio.com/blog/guide_to_404_pages_with_expressionengine/
Boyink — 11:23 on 08.30.2011
So, just to be clear, the challenge is one template that handles the index/multi-entry view with pagination and single-entry view?
If that’s the case then while I haven’t looked at Switchee Masugas Detect Page Type plugin has worked for me.
Index view:
http://boyink.com/write/
Paginated Index:
http://boyink.com/write/P5/
Single Entry:
http://boyink.com/write/show-dont-tell/
Tony Geer — 11:31 on 08.30.2011
Hey Mike. That is indeed what we’re trying to do. But you should find Switchee more efficient than the Detect Page Type plugin as it doesn’t evaluate conditions that aren’t met: http://expressionengine.com/archived_forums/viewthread/145805/#717687
Boyink — 11:37 on 08.30.2011
So Switchee does work for pagination - but you have to use the regex code?
More curious than anything…not in the mode to go back and re-factor boyink.com just yet…;)
Tony Geer — 11:47 on 08.30.2011
Yep and yep. For me, this is one of those good-to-know tips that will
a. come in handy for high-traffic sites where every bit of optimization will pay off
b. be used for sites going forward when it’s applicable
Mark Croxton — 12:44 on 09.23.2011
Hi Tony,
thanks for writing about Switchee!
I thought I’d just add that the latest Switchee supports unlimited nesting, so now you can do even more in a single template.
https://github.com/croxton/Switchee/tree/develop