jonassiewertsen / statamic-livewire

A Laravel Livewire integration for Statamics antlers engine
91 stars 16 forks source link

Statamic's static caching breaks Livewire #36

Closed aerni closed 1 year ago

aerni commented 1 year ago

Statamic's static caching breaks Livewire. Components stop working when loading a cached page. This is true for both half and full-measure static caching. I tested it on a fresh Statamic installation with v3.0.0-beta.1 of this addon.

The issue can be reproduced with this sample Counter component. I tested with a blade.php and antlers.html component view. It doesn't make a difference.

I'm unsure if the problem is Livewire, this addon, or Statamic. I haven't had time to dig deeper.

Component class:

<?php

namespace App\Livewire;

use Livewire\Component;

class Counter extends Component
{
    public $count = 1;

    public function increment()
    {
        $this->count++;
    }

    public function decrement()
    {
        $this->count--;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}

Component view

<div>
    <h1>{{ $count }}</h1>

    <button wire:click="increment">+</button>

    <button wire:click="decrement">-</button>
</div>

Statamic template

{{ livewire:counter }}
marcorieser commented 1 year ago

Does the component work with v2? I didnt understand the cache part (key) when implementing v3 component rendering.

jonassiewertsen commented 1 year ago

In V2 you had to make sure that Livewire scripts were not cached.

I didn‘t look into it yet, but that could be a way to start.

marcorieser commented 1 year ago

Good point @jonassiewertsen.

Seems like Livewire does not inject the styles/scripts automatically when half/full cache is enabled. As soon as I add them manually into the layout, the component works fine.

jonassiewertsen commented 1 year ago

Interesting! Simply adding the tags manually to make it usable if caching is turned on is a great workaround.

Before using this as a permanent fix, I would like to figure out how complex it is to force the style and script injection if caching is turned on.

jonassiewertsen commented 1 year ago

I can't reproduce this behavior with half-measure caching. Can others?

jonassiewertsen commented 1 year ago

@aerni I tested it locally (half-measure) and did nothing more than add 'half' to my static_caching settings. Did you do more than that?

aerni commented 1 year ago

I just tested on a fresh installation and can confirm that half-measure caching is also broken. Here's a repo that reproduces the issue: https://github.com/aerni/statamic-livewire-caching.

Just enable STATAMIC_STATIC_CACHING_STRATEGY=half in your .env and the counter won't work on subsequent loads.

robdekort commented 1 year ago

Livewire 3 seems to work on the first load when I manually include the styles and scripts, but after that I get this old baby back:

Screenshot 2023-09-12 at 14 06 23
robdekort commented 1 year ago

This is resolved by adding Livewire as a CSRF exception in VerifyCsrfToken.php:

protected $except = [
    '/livewire*'
];

Which I remember at some point wasn't needed anymore because of a PR @aerni did to the Statamic Core.

marcorieser commented 1 year ago

Which I remember at some point wasn't needed anymore because of a PR @aerni did to the Statamic Core.

Not sure if the endpoint changed

aerni commented 1 year ago

It's this PR: https://github.com/statamic/cms/pull/7894. It looks like Livewire changed its CSRF implementation as window.livewire_token doesn't return anything anymore. So Statamic can't replace the token anymore.

aerni commented 1 year ago

I played around with it some and found two solutions to the problem. Both solutions currently only work with half-measure static caching. I've created a Statamic PR that will also make it work with full-measure static caching.

Solution 1: Force loading the scripts with \Livewire\Livewire::forceAssetInjection(); in a service provider.

Solution 2: Adding the script to your layout with {{ livewire:scripts }} or {{ livewire:scriptConfig }}.

How it works: Livewire 3 is exposing the CSRF token as a data attribute on its script tag:

<script 
    src="/livewire/livewire.js?id=f41737f6"
    data-csrf="lAUYD16rDgXflgLYME6SFYoPUJaEjqqUDZmLxQRP" 
    data-uri="/livewire/update" 
    data-navigate-once="true">
</script>

When Statamic caches the page, it replaces the CSRF token with a placeholder.

<script 
    src="/livewire/livewire.js?id=f41737f6"   
    data-csrf="STATAMIC_CSRF_TOKEN" 
    data-uri="/livewire/update" 
    data-navigate-once="true">
</script>

This placeholder is then replaced with the current CSRF token whenever the cached page is loaded.

If you are manually bundling Livewire and Alpine, the script will look a little different but the concept stays the same:

<script data-navigate-once="true">
    window.livewireScriptConfig = {
        "csrf": "STATAMIC_CSRF_TOKEN",
        "uri":"\/livewire\/update",
        "progressBar":true
    };
</script>
jasonvarga commented 1 year ago

I just tested on a fresh installation and can confirm that half-measure caching is also broken. Here's a repo that reproduces the issue: aerni/statamic-livewire-caching.

In this repo, you are relying on the auto-injection feature.

Scripts only get injected if a Livewire component was rendered on that request. On subsequent requests, we're returning cached html early - the component rendering logic doesn't happen. That's why it's not working for you.

You can solve this by using the {{ livewire:styles }}&{{ livewire:scripts }} tags.

Or, use Livewire:: forceAssetInjection() but that still won't work with full-measure static caching, even with @aerni 's PR. (The page is written to html before livewire adds the injected assets, and we don't have a way to guarantee the middleware order)

aerni commented 1 year ago

Or, use Livewire:: forceAssetInjection() but that still won't work with full-measure static caching, even with @aerni 's PR. (The page is written to html before livewire adds the injected assets, and we don't have a way to guarantee the middleware order)

@jasonvarga Oh, you're right. I missed that the current Statamic Valet driver still hits PHP even for full-measure static caching. This is why the script was still added when using Livewire:: forceAssetInjection(). Thanks for the Valet PR btw. I saw that it just got merged!

So, to sum up:

jasonvarga commented 1 year ago

Correct!

The only change that this package should make is probably to:

jonassiewertsen commented 1 year ago

I released a new version:

https://github.com/jonassiewertsen/statamic-livewire/releases/tag/v3.0.0-beta.4