Skip to content

Caching

Mark Croxton edited this page Apr 20, 2017 · 30 revisions

Do cache

Global fragments: rendered partials, such as navigational elements, footers, sidebars and other reusable, globally visible page elements (scope="site").

Addressable pages: the unique output of individual pages in your site, where the URI alone is sufficient to generate all possible interaction states ("URI addressability").

Don't cache

Non-global fragments: Partials that generate output that is specific to the URI or user.

Non-addressable pages: where the URI of a page alone is not sufficient to determine it's interaction state. E.g. forms that POST data, shopping carts, 404 pages.

Dynamic content: which is dependent on a user session, form action or search query. E.g. Search results, logged-in member data, contact forms.

Cache planning

Build for URI addressability: as far as possible plan your site so that all interaction states are represented by a unique URI.

Stay DRY: separate out commonly used global page fragments into snippets, Low Variables or Stash embeds. Cache structured data, not marked-up data, so you can re-use the data with different markups.

Group like items: use context and/or bundles to create groups of related cached items, so that they can be targetted for cache-breaking.

Use layers: use fragment-caching inside page-caching to allow for progressive cache rebuilds and avoid 'stampedes'.

Common pitfalls, and how to avoid them

Domain aliases - Cached domain-specific absolute URLs can lead to serious complications like broken AJAX requests due to cross-domain restrictions. Ideally you should only allow access to your website via a single known domain. If you can't be sure of this, try to eliminate links containing absolute URLs entirely from your templates.

Non-strict URLs - It's very important to ensure that your templates return a 404 if additional segments are added to the URL or an entry doesn't exist. URL variants can bloat your cache.

Carefully plan your URL scheme

DO use addressable URIs

/products/shoes/mens/9

AVOID query strings (but if you must, don't cache these pages)

/products?cat=shoe&type=mens&size=9

AVOID absolute URLs in templates (unless you are sure your site will only be accessed via a single canonical domain)

http://shoes.com/assets/scripts/myscript.js
https://shoes.com/assets/scripts/myscript.js
https://shoes.co.uk/assets/scripts/myscript.js

AVOID random entries (but if you must, don't cache these pages)

{exp:channel:entries orderby="random"}

DON'T cache page 2+ of paginated lists

/products/shoes/mens/9/P30

DON'T cache 404s or invalid URIs

/404
/products/shoes/mens/9/unexpected-extra-segment
/products/shoes/mens/hey-i-should-be-a-number

Before caching, validate the routes into your website:

{!-- no results --}
{if no_results}
   {redirect="404"}
{/if}

{!-- additional segments --}
{if segment_4}
   {redirect="404"}
{/if}

I highly recommend using Resource Router to define and validate known routes:

'blog/:url_title' => function($router, $wildcard) {
   // valid blog entry (channel 2)?
   if ($wildcard->isValidUrlTitle(array('channel_id' => 2))) {
      
      // route to template
      $router->setTemplate('blog/post');
   }
},

// any other rules here

// send any other non-matching routes to 404
'.*' => function($router) {
    $router->set404();
},

Page caching examples

Static caching

{!-- simply add to the bottom of the template --}
{exp:stash:static logged_out_only="yes"}

Database caching, escaping some regions so they remain dynamic

{exp:stash:cache unprefix="entry"}

	{stash:embed:layouts:standard}

	{!-- capture channel data for a single entry --}  
	{exp:channel:entries channel="blog" limit="1"}    

		{exp:stash:set}        
	    	{stash:pg_title}{title}{/stash:pg_title}       
	    	{stash:pg_intro}{cf_blog_intro}{/stash:pg_intro}        
	    	{stash:pg_body}{cf_blog_body}{/stash:pg_body}     
		{/exp:stash:set} 

		{if entry:no_results}
			{redirect="404"}
		{/if}

	{/exp:channel:entries}
	
	{!-- this will be escaped --}   
	{stash:nocache}     
	   Hi, {screen_name}   
	{/stash:nocache}

{/exp:stash:cache}

Database caching with stash embeds

{!-- templates/default_site/blog/index.html --}
{stash:embed  
 	name="@URI:base"  
 	file_name="viewmodels:base_vm"  
 	parse_stage="both"
}

{!-- stash_templates/viewmodels/base_vm.html --}
{stash:embed:layouts:standard}

{!-- capture channel data for a single entry --}  
{exp:channel:entries channel="blog" limit="1"}       
 	{exp:stash:set}        
    	{stash:pg_title}{title}{/stash:pg_title}       
    	{stash:pg_intro}{cf_blog_intro}{/stash:pg_intro}        
    	{stash:pg_body}{cf_blog_body}{/stash:pg_body}     
 	{/exp:stash:set} 

 	{if no_results}
 		{redirect="404"}
 	{/if}
{/exp:channel:entries}

{!-- this will be escaped --}   
{stash:nocache}     
	Hi, {screen_name}   
{/stash:nocache}

Fragment caching

{!-- save a list of 5 latest jobs for an hour --}
{exp:stash:set_list 	
	 name="jobs"	
	 parse="yes"	
	 save="yes"	
	 scope="site"	
	 refresh="60"	
	 replace="no"
}	
	{exp:channel:entries channel="jobs" limit="5"}			
	    {stash:job_title}{title}{/stash:pg_title}			
	    {stash:job_url}{url_title}{/stash:job_url}	
	{/exp:channel:entries}
{/exp:stash:set_list}

{!-- save a html fragment for an hour, and output --}
{exp:stash:set
   name="my_job"
   parse="yes"
   save="yes"
   scope="site"
   refresh="60"
   replace="no"
   output="yes"
}
  	{exp:channel:entries channel="jobs" limit="1"}			
	    {title}
	{/exp:channel:entries}

{/exp:stash:set}
Clone this wiki locally