Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

warpig's avatar
Level 12

why does livewirescripts makes page load slow

ive introduced livewirestyles on header and livewirescripts below closing body tag and upon inserting the scripts ive noticed a change in loading times whenever the page refreshes. the component is being used on layouts/app which is spread across most of the site.

As far as the name of the livewirescript on the network tab im unfamiliar with it but i found this under the name of "http://localhost/livewire/livewire.js?id=90730a3b0e7144480175"

Queued: 284 msStarted: 284 msDownloaded: 284 ms

this is what the bottom of the ctrl + i tab says of the response on the page after introducing livewirescripts

15 requests
3.38 kB / 411 B transferred
Finish: 4.95 min
DOMContentLoaded: 1.47 s
load: 3 s

and when i remove it:

14 requests
3.38 kB / 411 B transferred
Finish: 12.41 s
DOMContentLoaded: 853 ms
load: 1.82 s

(usually 1 or 2s)

so apparently request #15 makes the page slow, why and how. thanks

0 likes
17 replies
warpig's avatar
Level 12

this is the component:

<?php

namespace App\Http\Livewire;

use App\Models\Post;
use Livewire\Component;;
use App\Models\UpvoteDownvote as AppUpvoteDownvote;

class UpvoteDownvote extends Component
{
    public Post $post;

    public function mount(Post $post)
    {
        $this->post = $post;
    }
    
    public function render()
    {
        $upvotes = AppUpvoteDownvote::where('post_id', '=', $this->post->id)
            ->where('is_upvote', '=', true)
            ->count();

        $downvotes = AppUpvoteDownvote::where('post_id', '=', $this->post->id)
            ->where('is_upvote', '=', false)
            ->count();

        // The status whether current user has upvoted the post or not
        // This will be null, true or false
        // null means user has done upvote or downvote
        $hasUpvote = null;

        /** @var \App\Models\User $user */
        $user = request()->user();

        if($user) {
            $upvoteDownvote = AppUpvoteDownvote::where('post_id', '=', $this->post->id)
                ->where('user_id', '=', $user->id)
                ->first();
            if($upvoteDownvote) {
                $hasUpvote = !!$upvoteDownvote->is_upvote;
            }
        }
        
        return view('livewire.upvote-downvote', compact('upvotes', 'downvotes', 'hasUpvote'));
    }

    public function upvoteDownvote($upvote = true)
    {
        /** @var \App\Models\User $user */
        $user = request()->user();

        if(!$user) {
            return $this->redirect('login');
        }

        if (!$user->hasVerifiedEmail()) {
            return $this->redirect(route('verification.notice'));
        }

        $upvoteDownvote = AppUpvoteDownvote::where('post_id', '=', $this->post->id)
            ->where('user_id', '=', $user->id)
            ->first();

        if(!$upvoteDownvote) {
            AppUpvoteDownvote::create([
                'is_upvote' => $upvote,
                'post_id' => $this->post->id,
                'user_id' => $user->id
            ]);
            
            return;
        }

        if ($upvote && $upvoteDownvote->is_upvote || !$upvote && !$upvoteDownvote->is_upvote) {
            $upvoteDownvote->delete();
        } else {
            $upvoteDownvote->is_upvote = $upvote;
            $upvoteDownvote->save();
        }

    }
}
Snapey's avatar

You have some other Livewire component on the page that you have forgot about?

Snapey's avatar

Loading the script, allows the component to run, but your non-livewire page load time at 12 seconds is also terrible.

Are you loading loads of posts and including the component for every post?

warpig's avatar
Level 12

@Snapey

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use App\Models\Category;
use App\Models\PostView;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function home()
    {
        //show the latest post created
        $latestPost = Post::where('active', '=', 1)
            ->where('published_at', '<', Carbon::now())
            ->orderBy('published_at', 'desc')
            ->with('categories')
            ->limit(1)
            ->first();

        // show the 3 most popular posts
        $popularPosts = Post::query()
            ->leftJoin('upvote_downvotes', 'post_id', '=', 'upvote_downvotes.post_id')
            ->select('posts.*', DB::raw('COUNT(upvote_downvotes.id) as upvote_count'))
            ->where(function($query) {
                $query->whereNull('upvote_downvotes.is_upvote')
                    ->orWhere('upvote_downvotes.is_upvote', '=', 1);
            })
            ->where('active', '=', 1)
            ->where('published_at', '<', Carbon::now())
            ->orderByDesc('upvote_count')
            ->groupBy('posts.id')
            ->limit(5)
            ->get();

        // show recent categories with latest posts
        $categories = Category::query()
            // ->with(['posts' => function ($query) {
            //     $query->orderByDesc('published_at')->limit(3);
            // }])
            ->whereHas('posts', function($query) {
                $query->where('active', '=', 1)
                      ->where('published_at', '<', Carbon::now());
            })
            ->select('categories.*')
            ->selectRaw('MAX(posts.published_at) as max_date')
            ->leftJoin('category_post', 'categories.id', '=', 'category_post.category_id')
            ->leftJoin('posts', 'posts.id', '=', 'category_post.post_id')
            ->orderByDesc('max_date')
            ->groupBy('categories.id')
            ->limit(2)
            ->get();
        
        // if authorized show recommended posts based on user upvotes
        $user = auth()->user();

        if ($user) {
            $leftJoin = "(SELECT cp.category_id, cp.post_id FROM upvote_downvotes
                        JOIN category_post cp ON upvote_downvotes.post_id = cp.post_id
                        WHERE upvote_downvotes.is_upvote = 1 and upvote_downvotes.user_id = ?) as t";
            $recommendedPosts = Post::query()
                ->leftJoin('category_post as cp', 'posts.id', '=', 'cp.post_id')
                ->leftJoin(DB::raw($leftJoin), function ($join) {
                    $join->on('t.category_id', '=', 'cp.category_id')
                         ->on('t.post_id', '!=', 'cp.post_id');
                        })
                ->select('posts.*')
                ->where('posts.id', '!=', DB::raw('t.post_id'))
                ->setBindings([$user->id])
                ->limit(2)
                ->get();
        // not authorized = popular posts based on views
        } else {
            $recommendedPosts = Post::query()
                ->leftJoin('post_views', 'posts.id', '=', 'post_views.post_id')
                ->select('posts.*', DB::raw('COUNT(post_views.id) as view_count'))
                ->where('active', '=', 1)
                ->where('published_at', '<', Carbon::now())
                ->groupBy('posts.id')
                ->limit(2)
                ->get();
        }

        return view('home', compact(
            'latestPost', 
            'popularPosts', 
            'recommendedPosts', 
            'categories'
        ));
    }

    /**
     * Display the specified resource.
     */
    public function show(Post $post, Request $request)
    {
        if (!$post->active || $post->published_at > Carbon::now()) {
            throw new NotFoundHttpException();
        }
      
        $next = Post::query()
            ->where('active', true)
            ->where('published_at', '<=', Carbon::now())
            ->whereDate('published_at', '<', $post->published_at)
            ->orderBy('published_at', 'desc')
            ->limit(1)
            ->first();

        $prev = Post::query()
            ->where('active', true)
            ->where('published_at', '<=', Carbon::now())
            ->whereDate('published_at', '>', $post->published_at)
            ->orderBy('published_at', 'asc')
            ->limit(1)
            ->first();
        
        $user = $request->user();

        PostView::create([
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent(),
            'post_id' => $post->id,
            'user_id' => $user?->id
        ]);

        return view('post.view', compact('post', 'prev', 'next'));
    }

    public function byCategory(Category $category)
    {
        $categories = Category::query();

        $posts = Post::query()
            ->join('category_post', 'posts.id', '=', 'category_post.post_id')
            ->where('category_post.category_id', '=', $category->id)
            ->where('active', '=', true)
            ->whereDate('published_at', '<=', Carbon::now())
            ->orderBy('published_at', 'desc')
            ->paginate(10);

            return view('categories', compact('posts', 'categories', 'category'));
    }
}
Snapey's avatar

@warpig

Finish: 12.41 s. is rubbish whichever way you look af it

What sort of lighthouse score does that get?

warpig's avatar
Level 12

@Snapey oh, yeah not sure what gets loded after 12 seconds. The page doesnt take that long as the visual things. right now its taking 2 seconds, with the livewirescripts. i changed the script tag on to the component itself. i have a youtube video and there are some youtube things, being blocked by an extension (uBlock) and that seems to happen after everything has been loaded

warpig's avatar
Level 12

@snapey Are you loading loads of posts and including the component for every post?

loads of post and variables yes. above should give you an idea of how many there are, the component lives here under the upvotes and/or downvotes

<div class="flex gap-2 py-4 text-2xl">
    <button wire:click="upvoteDownvote()" class="flex items-center gap-2 hover:text-blue-500 transition-all {{ $hasUpvote ? 'text-blue-500' : '' }}">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
            <path stroke-linecap="round" stroke-linejoin="round" d="M6.633 10.5c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75A2.25 2.25 0 0116.5 4.5c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23H5.904M14.25 9h2.25M5.904 18.75c.083.205.173.405.27.602.197.4-.078.898-.523.898h-.908c-.889 0-1.713-.518-1.972-1.368a12 12 0 01-.521-3.507c0-1.553.295-3.036.831-4.398C3.387 10.203 4.167 9.75 5 9.75h1.053c.472 0 .745.556.5.96a8.958 8.958 0 00-1.302 4.665c0 1.194.232 2.333.654 3.375z" />
        </svg>
        {{ $upvotes }}
    </button>

    <button wire:click="upvoteDownvote(false)" class="flex items-center gap-2 hover:text-red-500 transition-all {{ $hasUpvote === false ? 'text-red-500' : '' }}">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
            <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 15h2.25m8.024-9.75c.011.05.028.1.052.148.591 1.2.924 2.55.924 3.977a8.96 8.96 0 01-.999 4.125m.023-8.25c-.076-.365.183-.75.575-.75h.908c.889 0 1.713.518 1.972 1.368.339 1.11.521 2.287.521 3.507 0 1.553-.295 3.036-.831 4.398C20.613 14.547 19.833 15 19 15h-1.053c-.472 0-.745-.556-.5-.96a8.95 8.95 0 00.303-.54m.023-8.25H16.48a4.5 4.5 0 01-1.423-.23l-3.114-1.04a4.5 4.5 0 00-1.423-.23H6.504c-.618 0-1.217.247-1.605.729A11.95 11.95 0 002.25 12c0 .434.023.863.068 1.285C2.427 14.306 3.346 15 4.372 15h3.126c.618 0 .991.724.725 1.282A7.471 7.471 0 007.5 19.5a2.25 2.25 0 002.25 2.25.75.75 0 00.75-.75v-.633c0-.573.11-1.14.322-1.672.304-.76.93-1.33 1.653-1.715a9.04 9.04 0 002.86-2.4c.498-.634 1.226-1.08 2.032-1.08h.384" />
        </svg>
        {{ $downvotes }}
    </button>
</div>
@livewireScripts
Snapey's avatar

recursion in YOUR code.

probably a component that loads a livewire component that ends up loading the same livewire component

Snapey's avatar

@warpig all signs that your html is a mess (as is 12 seconds render)

warpig's avatar
Level 12

@Snapey check this, where you think it breaks?

    <nav class="charcoal" x-data="{ open: false }">
        <div class="p-3">
            <div class="block md:hidden">
                <a
                    href="#"
                    class="md:hidden text-white text-lg font-bold uppercase text-center flex justify-center items-center"
                    @click="open = !open"
                >
                    Menu <i :class="open ? 'fa-chevron-down': 'fa-chevron-up'" class="fas ml-2"></i>
                </a>
            </div>
        </div> <!-- menu icon -->

        <div class="container mx-auto w-full lg:grid lg:items-center lg:grid-cols-5">
            <a class="" href="/">
                <img
                    class="w-48 mx-auto py-4 lg:col-start-1 lg:col-end-2" 
                    src="{{ \App\Models\TextWidget::getImage('header') }}"
                >
            </a> <!-- logo end -->

            <div 
                :class="open ? 'block': 'hidden'" 
                class="w-full flex-grow md:flex md:items-center md:w-auto lg:col-start-3 lg:col-end-5 mx-auto gap-3"
            >
                <div 
                    x-data="{ open: null }" 
                    class="w-full flex-grow sm:flex sm:items-center sm:w-auto bg-charcoal"
                >
                    <div class="mx-auto flex flex-col items-center justify-center sm:flex-row text-sm font-bold uppercase mt-0 px-6 py-2">
                        @foreach ($allCategories as $category)
                            <div 
                                class="py-2 relative w-full lg:w-auto" 
                                @mouseenter="open = '{{ $loop->index }}'" 
                                @mouseleave="open = null"
                            >
                                <div class="flex items-center justify-between md:flex-none">
                                    <a 
                                        href="{{ route('by-category', $category) }}" 
                                        class="text-white text-lg transition ease-out hover:bg-gray-100 hover:text-black rounded py-2 px-4 mx-2"
                                    >
                                        {{ $category->title }} 
                                    </a>
                                    @if ($category->parent_id == null && $category->subCategory()->count() > 0)
                                        <i :class="open === '{{ $loop->index }}' ? 'fa-chevron-up': 'fa-chevron-down'" 
                                            class="fas ml-2 text-white font-bold cursor-pointer text-lg" 
                                            @click="open = open === '{{ $loop->index }}' ? null : '{{ $loop->index }}'">
                                        </i>
                                </div>

                                <div x-cloak
                                    x-show="open === '{{ $loop->index }}'"
                                    @mouseenter="open = '{{ $loop->index }}'" 
                                    @mouseleave="open = null" 
                                    class="lg:w-44 w-full border-2 border-red-600 z-40 bg-black inline-flex flex-col justify-center items-center relative lg:absolute top-full left-0 right-0 p-3"
                                >
                                    @foreach($category->subCategory as $subCategory)  
                                        <a 
                                            href="{{ route('by-category', $subCategory) }}" 
                                            class="text-white text-center text-lg transition ease-out hover:bg-gray-100 hover:text-black rounded p-2"
                                        >
                                            {{ $subCategory->title }}
                                        </a>
                                    @endforeach

                                    @else
                                        <!-- this category has no subcategories -->
                                    @endif
                                </div> <!-- subcategories menu end -->
                            </div> <!-- categories menu end -->
                        @endforeach
                            <a href="{{ route('about') }}" class="w-full text-white text-lg transition ease-out hover:bg-gray-100 hover:text-black rounded p-2 ml-7 md:ml-0">
                                About
                            </a>
                    </div>
                </div>
            </div>
            @auth()
                <!-- Settings Dropdown -->
                <div class="sm:flex sm:items-center sm:ml-6">
                    <x-dropdown align="custom" width="48">
                        <x-slot name="trigger">
                            <button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
                                <div>{{ Auth::user()->name }}</div>

                                <div class="ml-1">
                                    <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                                        <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                                    </svg>
                                </div>
                            </button>
                        </x-slot>
                        <x-slot name="content">
                            <x-dropdown-link :href="route('profile.edit')">
                                {{ __('Profile') }}
                            </x-dropdown-link>

                            <!-- Authentication -->
                            <form method="POST" action="{{ route('logout') }}">
                                @csrf

                                <x-dropdown-link :href="route('logout')"
                                        onclick="event.preventDefault();
                                                    this.closest('form').submit();">
                                    {{ __('Log Out') }}
                                </x-dropdown-link>
                            </form>
                        </x-slot>
                    </x-dropdown>
                </div>
            @else
                <div class="flex justify-center md:items-end gap-x-4 py-4 lg:py-0 lg:flex-col md:text-center w-full">
                    <a href="{{ route('login') }}" class="md:text-black lg:text-white text-lg transition ease-out lg:bg-transparent bg-gray-200 p-2 hover:bg-gray-100 hover:text-black py-2">Login</a>
                    <a href="{{ route('register') }}" class="md:text-black lg:text-white text-lg transition ease-out lg:bg-transparent bg-gray-200 p-2 hover:bg-gray-100 hover:text-black py-2">Register</a>
                </div>
            @endauth
        </div> <!-- desktop menu end -->
    </nav> <!-- navbar end -->
warpig's avatar
Level 12

i was loading tailwind and alpinejs twice, so i removed the cdn links and think i'll keep the vite scripts ? does it even matter which? that made the page finish loading at 2.79s or even as low as 1.79 at first load

so i'll just keep this:

    <!-- Scripts -->
    @vite(['resources/css/app.css', 'resources/js/app.js'])

Please or to participate in this conversation.