jonassiewertsen / statamic-livewire

A Laravel Livewire integration for Statamics antlers engine
83 stars 14 forks source link

Statamic's static caching breaks Livewire #36

Closed aerni closed 11 months ago

aerni commented 11 months 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 11 months ago

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

jonassiewertsen commented 11 months 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 11 months 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 11 months 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 11 months ago

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

jonassiewertsen commented 11 months 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 11 months 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 11 months 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 11 months 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 11 months 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 11 months 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 11 months 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 11 months 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 11 months 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 11 months ago

Correct!

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

jonassiewertsen commented 11 months ago

I released a new version:

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