Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nocache tag #855

Merged
merged 4 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions content/collections/docs/static-caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**.
Expand Down Expand Up @@ -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.
Expand All @@ -72,6 +76,10 @@ return [
];
```

:::tip Heads up!
When using full-measure caching, the [nocache tag](/tags/nocache) will rely on JavaScript.
:::

## 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.
Expand Down Expand Up @@ -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 [
Expand All @@ -125,6 +133,14 @@ 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 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. ([With some exceptions](#csrf-tokens))
:::

## Invalidation

A statically cached page will be served until it is invalidated. You have a several options for how to invalidate your cache.
Expand Down Expand Up @@ -245,7 +261,7 @@ return [
```
### Rewrite Rules

This multi-site example needs modified rewrite rules.
This multi-site example needs modified rewrite rules.

#### Apache

Expand Down Expand Up @@ -275,3 +291,42 @@ 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](#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!++]
]
```

### CSRF Tokens

When using half measure, CSRF tokens will be replaced without any caveats.

When using full measure, tokens will automatically be replaced in `<input>` and `<meta>` tags where their value/content is the token.

```
<meta name="csrf-token" content="{{ csrf_token }}" />
<input type="hidden" value="{{ csrf_token }}" />
```

If you need to output a CSRF token in another place while using full measure, you'll need to use nocache tags.

```
<span>
{{ nocache }} {{# [tl!++] #}}
{{ csrf_token }}
{{ /nocache }} {{# [tl!++] #}}
</span>
```
18 changes: 16 additions & 2 deletions content/collections/tags/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
185 changes: 185 additions & 0 deletions content/collections/tags/nocache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
---
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.

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

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 }}
<li>
{{ company }}
{{ symbol }}
{{ get_price :of="symbol" }}
</li>
{{ /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 }}
<li>
{{ company }}
{{ symbol }}
{{ nocache }} {{# [tl!++] #}}
{{ get_price :of="symbol" }}
{{ /nocache }} {{# [tl!++] #}}
</li>
{{ /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 }}
<div class="movie">
{{ nocache }}
{{ name }} {{ rating }} {{ title }}
{{ /nocache }}
</div>
{{ /reviews }}
```

```
<div class="movie"> Top Gun 58% Movie Reviews </div>
<div class="movie"> Citizen Kane 99% Movie Reviews </div>
<div class="movie"> Jack and Jill 3% Movie Reviews </div>
```

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:

```
<div class="movie"> Top Gun 58% Ratings </div>
```

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 }}
<div class="movie">
{{ nocache }} {{# [tl!--] #}}
{{ name }} {{ rating }} {{ title }}
{{ /nocache }} {{# [tl!--] #}}
</div>
{{ /reviews }}
{{ /nocache }} {{# [tl!++] #}}
```
```
<div class="movie"> Top Gun 60% Ratings </div>
```

## 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 `</body>` 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 `<span>` 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('<svg>...</svg>');
```