From 2e6b7966246e7484976ecc7bf8cb41c9032fd3ef Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 23 Jun 2022 12:57:03 -0400 Subject: [PATCH 1/4] nocache tag --- content/collections/tags/nocache.md | 157 ++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 content/collections/tags/nocache.md diff --git a/content/collections/tags/nocache.md b/content/collections/tags/nocache.md new file mode 100644 index 000000000..83db9b620 --- /dev/null +++ b/content/collections/tags/nocache.md @@ -0,0 +1,157 @@ +--- +id: 221fadae-5284-42a6-a1df-aad326e5fa70 +blueprint: tag +title: 'No Cache' +description: 'Keeps a chunk of your template dynamic inside of an otherwise cached area.' +intro: When using a broader caching solution (like [static caching](/static-caching) or the [cache tag](/tags/cache)) you may want to keep a chunk of your template dynamic. +--- +## Overview + +When using [Static Caching](/static-caching) or the [cache tag](/tags/cache), the rendered contents will be remembered for the next request. That's great for performance, but if you have something in there that you'd like to keep dynamic (such as authenticated user data, randomized elements, or listings), you'd normally have to disable static caching for the whole page. + +With the `nocache` tag, you can keep the entirety of the page cached (when using Static Caching) or chunks of the page cached (when using the cache tag), with certain parts left as as dynamic: + +``` +{{ nav }} ... {{ /nav }} + +{{ nocache }} {{# [tl! focus:6] #}} + {{ if logged_in }} + Welcome back, {{ current_user:name }} + {{ else }} + Hello, Guest! + {{ /if }} +{{ /nocache }} + +{{ content }} +``` + +## Forms + +Before the `nocache` tag existed, a common reason to exclude a page from static caching would be if there was a form on there. + +Forms contain a CSRF token for security, which is unique to each user visiting. If you submit the form using someone elses token (which could happen when static caching is enabled) the form won't work. + +You may think you can now wrap your `form` tags with `nocache`, but CSRF tokens are now automatically made dynamic. **You don't need to do anything!** + +## Blade + +You can use the "no cache" feature in Blade too, although you should use the dedicated Blade directive instead of trying to use the tag. + +To keep a part of your template dynamic, move it into a partial then use the `@nocache` directive just like you would use the `@include` directive. + +```blade +@include('mypartial') {{-- this will be cached --}} +@nocache('mypartial') {{-- this will be dynamic --}} +``` + +## Caveats + +Most of the time you can probably get away with wrapping something in a `nocache` tag and it will just work! However there are some situations where you will need to be more aware of how the tag works in order to get predictable results. + + +### When to wrap + +For example, let's say you have a collection of stocks. Each entry contains information about the stock, except for the price. You have a custom tag that will get the up-to-the-second stock price using an API. + +``` +{{ collection:stocks }} +
  • + {{ company }} + {{ symbol }} + {{ get_price :of="symbol" }} +
  • +{{ /collection:stocks }} +``` + +If you turned on static caching now, then the whole listing would be cached, including the prices. + +In this example, you should put a `nocache` _inside_ the loop. + +``` +{{ collection:stocks }} +
  • + {{ company }} + {{ symbol }} + {{ nocache }} {{# [tl!++] #}} + {{ get_price :of="symbol" }} + {{ /nocache }} {{# [tl!++] #}} +
  • +{{ /collection:stocks }} +``` + +By putting it inside the loop, it means that the `collection:stocks` tag wouldn't need to re-query for entries on each subsequent request. + +You'd then also rely on [static caching invalidation rules](/static-caching#invalidation) to invalidate the entire page when one of your stock entries change. + +An example where you would want to put a `nocache` tag _around_ a loop would be when re-querying every request would be important. For example, a randomized section: + +``` +{{ nocache }} + {{ collection:blog featured:is="true" sort="random" }} + ... + {{ /collection:blog }} +{{ /nocache }} +``` + +### Context + +The `nocache` tag will remember the "context", which are the variables available at that point in the template. + +Here we'll use a loop to illustrate what happens: + +```yaml +title: Movie Reviews +reviews: + - name: Top Gun + rating: 58% + - name: Citizen Kane + rating: 99% + - name: Jack and Jill + rating: 3% +``` + +``` +{{ reviews }} +
    + {{ nocache }} + {{ name }} {{ rating }} {{ title }} + {{ /nocache }} +
    +{{ /reviews }} +``` + +``` +
    Top Gun 58% Movie Reviews
    +
    Citizen Kane 99% Movie Reviews
    +
    Jack and Jill 3% Movie Reviews
    +``` + +Within each of those nocache tags, you'd have access to the iteration specific variables: + - `name`, `rating` (e.g. `name` would be `Top Gun`, then `Citizen Kane`, etc) + - special loop variables (e.g. `first`, `last`, `count`) + - any cascading variables (e.g. from outer tags, or top level page variables like `now`, `page`, etc) + +All of the [top level variables](/variables) will be filtered out and replaced with fresh versions on subsequent requests. + +Now lets say you update `title` to `Ratings` and Top Gun's `rating` to `60%`. The first line out of output will now look like this: + +``` +
    Top Gun 58% Ratings
    +``` + +Only the `title`'s change would be reflected, since the `reviews` value was remembered. If you wanted this to be dynamic, you should wrap the loop rather than putting it inside the loop, as explained in [when to wrap](#when-to-wrap). + +``` +{{ nocache }} {{# [tl!++] #}} + {{ reviews }} +
    + {{ nocache }} {{# [tl!--] #}} + {{ name }} {{ rating }} {{ title }} + {{ /nocache }} {{# [tl!--] #}} +
    + {{ /reviews }} +{{ /nocache }} {{# [tl!++] #}} +``` +``` +
    Top Gun 60% Ratings
    +``` From 4f5ef55896be449948d7e25ba78d1cd39343e043 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 23 Jun 2022 12:57:17 -0400 Subject: [PATCH 2/4] mention nocache tag elsewhere --- content/collections/docs/static-caching.md | 18 +++++++++++++++--- content/collections/tags/cache.md | 18 ++++++++++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/content/collections/docs/static-caching.md b/content/collections/docs/static-caching.md index 6a4f7827e..8f6af9197 100644 --- a/content/collections/docs/static-caching.md +++ b/content/collections/docs/static-caching.md @@ -8,7 +8,7 @@ id: ffa24da8-3fee-4fc9-a81b-fcae8917bd74 --- ## Important Preface -Certain features — such as forms with server-side validation or content randomization — don’t work with static page caching. As long as you understand that, you can leverage static caching for maximum performance. +Certain features — such as forms with server-side validation or content randomization — may not work with static page caching. (You may want to check out the [nocache tag](/tags/nocache) though.) As long as you understand that, you can leverage static caching for maximum performance. :::tip You can **alternatively** use the [static site generator](https://github.com/statamic/ssg) to pre-generate and deploy **fully static HTML sites**. @@ -53,6 +53,10 @@ return [ ]; ``` +:::tip +You may use the [nocache tag](/tags/nocache) to keep parts of your pages dynamic. +::: + ## File Driver The file driver will generate completely static `.html` pages ready for your web server to serve directly. This means that the HTML files will be loaded before it even reaches PHP. @@ -72,6 +76,10 @@ return [ ]; ``` +:::tip Heads up! +When using full-measure caching, the [nocache tag](/tags/nocache) **will not work**. +::: + ## Server Rewrite Rules You will need to configure its rewrite rules when using full measure caching. Here are the rules for each type of server. @@ -111,7 +119,7 @@ On Windows IIS servers, your rewrite rules can be placed in a `web.config` file. ## Excluding Pages -You may add a list of URLs you wish to exclude from being cached. Pages with forms and listings with `sort="random"` are two common examples of pages that don't work properly when cached statically. +You may add a list of URLs you wish to exclude from being cached. ``` php return [ @@ -125,6 +133,10 @@ return [ Query strings will be omitted from exclusion rules automatically, regardless of whether wildcards are used. For example, choosing to ignore `/blog` will also ignore `/blog?page=2`, etc. +:::tip +Rather than excluding entire pages, you may consider using the [nocache tag](/tags/nocache) to keep parts of your page dynamic, like listings or randomized areas. +::: + ## Invalidation A statically cached page will be served until it is invalidated. You have a several options for how to invalidate your cache. @@ -245,7 +257,7 @@ return [ ``` ### Rewrite Rules -This multi-site example needs modified rewrite rules. +This multi-site example needs modified rewrite rules. #### Apache diff --git a/content/collections/tags/cache.md b/content/collections/tags/cache.md index 30cc47194..c00d91399 100644 --- a/content/collections/tags/cache.md +++ b/content/collections/tags/cache.md @@ -50,6 +50,20 @@ return [ ``` ::: +## Exclusions + +You may use the `nocache` tag inside a `cache` tag to keep that section dynamic. + +``` +{{ cache }} + this will be cached + {{ nocache }} this will remain dynamic {{ /nocache }} + this will also be cached +{{ /cache }} +``` + +[Read more about the nocache tag](/tags/nocache) + ## Invalidation Caching is handy to speed up parts of your site, but it's not very useful unless it's able to be updated at some stage. Here's how @@ -159,7 +173,7 @@ The `scope` parameter has no effect if you use the `key` parameter. You're free to use the cache tag on top of [static caching](/static-caching). You'll probably have static caching disabled during development so you can see your changes without having to continually clear anything. -The cache tag could be a nice compromise to speed up heavy areas for a few minutes at a time. Or, if you have some pages excluded -from static caching (like pages with forms) then the cache tag could be useful there. + +The cache tag could be a nice compromise to speed up heavy areas for a few minutes at a time. Or, if you have some pages excluded from static caching then the cache tag could be useful there. Of course, if you *do* have static caching enabled, keep in mind that you aren't going to gain anything by using both at the same time. From c30540b6bea3c5208a246c8a01beb0c514ceb53f Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 6 Jul 2022 10:54:24 -0400 Subject: [PATCH 3/4] more --- content/collections/docs/static-caching.md | 24 +++++++++++++++++++++- content/collections/tags/nocache.md | 18 ++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/content/collections/docs/static-caching.md b/content/collections/docs/static-caching.md index 8f6af9197..013a6404d 100644 --- a/content/collections/docs/static-caching.md +++ b/content/collections/docs/static-caching.md @@ -77,7 +77,7 @@ return [ ``` :::tip Heads up! -When using full-measure caching, the [nocache tag](/tags/nocache) **will not work**. +When using full-measure caching, the [nocache tag](/tags/nocache) will rely on JavaScript. ::: ## Server Rewrite Rules @@ -137,6 +137,10 @@ Query strings will be omitted from exclusion rules automatically, regardless of Rather than excluding entire pages, you may consider using the [nocache tag](/tags/nocache) to keep parts of your page dynamic, like listings or randomized areas. ::: +:::tip Another tip +CSRF tokens will automatically be excluded from the cache. You don't even need to use a `nocache` tag for that. +::: + ## Invalidation A statically cached page will be served until it is invalidated. You have a several options for how to invalidate your cache. @@ -287,3 +291,21 @@ location / { :::tip `{SERVER_NAME}` is used here instead of `{HTTP_HOST}` because `{HTTP_HOST}` may include the port. ::: + +## Replacers + +When a page is being statically cached on the first request, or loaded on subsequent requests, they are sent through "replacers". + +Statamic includes two replacers out of the box. One will replace CSRF tokens, the other will handle [nocache](/tags/nocache) tag usages. + +A replacer is a class that implements a `Statamic\StaticCaching\NoCache\Replacer` interface. You will be passed responses to the appropriate methods where you can adjust them as necessary. + +You can then enable your class by adding it to `config/statamic/static_caching.php`: + +```php +'replacers' => [ + CsrfTokenReplacer::class, + NoCacheReplacer::class, + MyReplacer::class, // [tl!++] +] +``` diff --git a/content/collections/tags/nocache.md b/content/collections/tags/nocache.md index 83db9b620..84a91cd63 100644 --- a/content/collections/tags/nocache.md +++ b/content/collections/tags/nocache.md @@ -155,3 +155,21 @@ Only the `title`'s change would be reflected, since the `reviews` value was reme ```
    Top Gun 60% Ratings
    ``` + +## Full Measure Static Caching + +When using the `file` static cache driver (aka. "full measure") the pages will be stored as plain `html` files on your server. + +On any pages that use a `nocache` tag, a small snippet of JavaScript will be injected just before the closing `` tag. + +The nocache fragments will be retrieved from the server using an AJAX request. Because of this, there may be a slight delay before the fragments are replaced. This is similar to a "FOUC" or "flash of unstyled content". In this case, there will be empty `` tags until they are replaced by the real fragments. + +You can optionally define the inner html of these `span` tags, if you wanted to have a "loading" state, for example. + +```php +use Statamic\Facades\StaticCache; + +StaticCache::nocachePlaceholder('Loading...'); +// or +StaticCache::nocachePlaceholder('...'); +``` From 6810a4e4d9c76a0fe3b35b22f0b93f0670ba0c68 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 12 Jul 2022 14:53:07 -0400 Subject: [PATCH 4/4] csrf token --- content/collections/docs/static-caching.md | 27 +++++++++++++++++++--- content/collections/tags/nocache.md | 12 +++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/content/collections/docs/static-caching.md b/content/collections/docs/static-caching.md index 013a6404d..06e717024 100644 --- a/content/collections/docs/static-caching.md +++ b/content/collections/docs/static-caching.md @@ -134,11 +134,11 @@ return [ Query strings will be omitted from exclusion rules automatically, regardless of whether wildcards are used. For example, choosing to ignore `/blog` will also ignore `/blog?page=2`, etc. :::tip -Rather than excluding entire pages, you may consider using the [nocache tag](/tags/nocache) to keep parts of your page dynamic, like listings or randomized areas. +Rather than excluding entire pages, you may consider using the [nocache tag](/tags/nocache) to keep parts of your page dynamic, like forms, listings, or randomized areas. ::: :::tip Another tip -CSRF tokens will automatically be excluded from the cache. You don't even need to use a `nocache` tag for that. +CSRF tokens will automatically be excluded from the cache. You don't even need to use a `nocache` tag for that. ([With some exceptions](#csrf-tokens)) ::: ## Invalidation @@ -296,7 +296,7 @@ location / { When a page is being statically cached on the first request, or loaded on subsequent requests, they are sent through "replacers". -Statamic includes two replacers out of the box. One will replace CSRF tokens, the other will handle [nocache](/tags/nocache) tag usages. +Statamic includes two replacers out of the box. One will replace [CSRF tokens](#csrf-tokens), the other will handle [nocache](/tags/nocache) tag usages. A replacer is a class that implements a `Statamic\StaticCaching\NoCache\Replacer` interface. You will be passed responses to the appropriate methods where you can adjust them as necessary. @@ -309,3 +309,24 @@ You can then enable your class by adding it to `config/statamic/static_caching.p MyReplacer::class, // [tl!++] ] ``` + +### CSRF Tokens + +When using half measure, CSRF tokens will be replaced without any caveats. + +When using full measure, tokens will automatically be replaced in `` and `` tags where their value/content is the token. + +``` + + +``` + +If you need to output a CSRF token in another place while using full measure, you'll need to use nocache tags. + +``` + +{{ nocache }} {{# [tl!++] #}} + {{ csrf_token }} +{{ /nocache }} {{# [tl!++] #}} + +``` diff --git a/content/collections/tags/nocache.md b/content/collections/tags/nocache.md index 84a91cd63..030263a2e 100644 --- a/content/collections/tags/nocache.md +++ b/content/collections/tags/nocache.md @@ -31,7 +31,17 @@ Before the `nocache` tag existed, a common reason to exclude a page from static Forms contain a CSRF token for security, which is unique to each user visiting. If you submit the form using someone elses token (which could happen when static caching is enabled) the form won't work. -You may think you can now wrap your `form` tags with `nocache`, but CSRF tokens are now automatically made dynamic. **You don't need to do anything!** +CSRF tokens are now updated automatically. + +However, if your form has logic in it, like validation or success messages, you'll need to keep the `form:create` dynamic by wrapping in `nocache` tags. + +``` +{{ nocache }} {{# [tl!++] #}} + {{ form:create }} + ... + {{ /form:create }} +{{ /nocache }} {{# [tl!++] #}} +``` ## Blade