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_title
of 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_2
and the second part of the conditional assumes thatsegment_2
is always theurl_title
of an entry, so theif no_results
conditional 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!