Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pjax Bundle Laravel Package

gremo/pjax-bundle

Symfony bundle adding PJAX support, combining AJAX with pushState for faster navigation by swapping only changed page content. Hooks into Symfony's event system to detect PJAX requests and serve partial responses, cutting full page reloads with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: the package is a Symfony bundle, archived, with 4 stars and zero dependents. For a Laravel codebase it is not installable, not maintained, and not a supply-chain risk you need to take. Treat it as a reference implementation of a pattern, not a dependency. If you are actually in a Symfony app, composer require gremo/pjax-bundle plus bundle registration works, but you are adopting dead code.

Where to look first (in the repo, as documentation):

  1. The Twig extension — it exposes the single primitive everything else builds on: "is this request a PJAX request?" (typically gremo_pjax_is_pjax()).
  2. The DI extension / config tree — the gremo_pjax keys (container selector, link selector, timeout) are just the JS plugin options echoed into a template.
  3. The Resources/views snippets — the layout-splitting convention is the whole idea.

The first use case is always the same: a paginated, filterable index page. Cold load renders the full chrome (nav, sidebar, footer, asset tags). Clicking a pagination link or a filter re-renders only the results container. The bundle's contribution is making step two cheap — the server returns the same controller and template, minus the outer layout.

Laravel equivalent, day one. The client side is unchanged (jquery-pjax sends X-PJAX: true and X-PJAX-Container: <selector>). Server side you have two options:

// Option A: hand-rolled middleware (no dependency, ~20 lines)
public function handle($request, Closure $next)
{
    $response = $next($request);

    if ($request->header('X-PJAX')) {
        $response->headers->set('Vary', 'X-PJAX', false);
        // Blade layout choice happens in the view, not here — see below.
    }

    return $response;
}
{{-- resources/views/posts/index.blade.php --}}
@extends(request()->header('X-PJAX') ? 'layouts.fragment' : 'layouts.app')

@section('content')
  <div id="pjax-container"> ... results ... </div>
@endsection

spatie/laravel-pjax does essentially this (middleware + Blade directive) and is the closest maintained analogue — worth reading before you write your own.

Implementation Patterns

Layout split via conditional @extends. The bundle's core trick, transliterated to Blade. Keep layouts.fragment deliberately dumb: no <html>, no <head>, no asset tags — just the container and its content. If layouts.fragment drifts from layouts.app the page will look correct on first load and subtly wrong after the first PJAX navigation.

Header-driven, not route-driven. Do not add a /pjax/... route prefix. The plugin sends X-PJAX (and _pjax=#selector as a query fallback), so the same URL serves both representations. This keeps deep links, SEO, and the back button working.

Container and link selectors are a contract between config and markup. The bundle's selector config must match the id of the element you wrap results in; the link_selector must match your pagination markup. When someone changes #pjax-container to #results and forgets the config, you get a silent full-page reload — no error.

Always set Vary: X-PJAX. This is the single most important integration tip and the most commonly skipped one. Without it, a CDN or reverse proxy will happily serve the fragment to a cold visitor (or vice versa). In Laravel, set it in the middleware above; in Symfony, in a response listener. Also make sure your cache key does not ignore the header.

Forms need extra work. jquery-pjax intercepts anchors only. Multi-step forms and filter forms need $.pjax.submit() wired to submit events, or the jquery-form plugin. Budget for this — the bundle gives you nothing here.

Rebinding JS on pjax:end. Anything initialized on DOMContentLoaded (date pickers, select2, tooltips, chart libs) will be dead after the first fragment swap. Centralize it:

function initWidgets() { /* select2, datepickers, etc. */ }
$(document).on('pjax:end', initWidgets).ready(initWidgets);

Redirects and X-PJAX-URL. PJAX follows 302s as full navigations in some plugin versions. When you need to change the address bar without reloading (e.g. after a sort change), have the server emit an X-PJAX-URL header on the fragment response.

Escalation path. If you find yourself writing pjax:end rebinding code in every file, that is the signal to move to Hotwire/Turbo or htmx — both are maintained, both handle forms/redirects/rebinding (turbo:load, hx-*) as first-class concerns, and both have Laravel packages (hotwired-laravel/turbo-laravel). The bundle predates all of this.

Gotchas and Tips

  • Scripts inside the fragment do not execute. Browsers ignore <script> inserted via innerHTML-style swaps. Any per-page JS must live in the outer layout or be re-invoked from pjax:end.
  • Analytics go dark. Virtual pageviews stop firing on PJAX navigation because the document never reloads. Hook pjax:end and push to your analytics tool, otherwise your traffic numbers silently drop for your most-used page.
  • Errors render inside the container. A 500 or 404 replaces your results div with the error page. Attach a pjax:error handler that falls back to window.location = xhr.url.
  • Back button / cacheBust. Older jquery-pjax defaults can serve stale fragments on browser-back. If state looks wrong after back/forward, set cacheBust: true and confirm the Vary header is correct.
  • Debugging checklist, in order: (1) Network tab — is X-PJAX: true on the request? If not, the plugin isn't intercepting (selector mismatch, or the link is outside the container and lacks data-pjax). (2) Is the response the fragment or the full page? If full, your header check is wrong — check middleware ordering and header casing. (3) Did it look right for a second and then flash? That's an accidental full navigation from a redirect.
  • Nested layouts bite hard. @extends inside @extends with a conditional in the middle produces confusing whitespace and duplicated container markup. Keep exactly one conditional @extends per view, at the top.
  • jquery-pjax is unmaintained upstream (defunkt archived it years ago). Even on Symfony, you are pairing an archived server bundle with an archived client plugin. Fine for a legacy app you are keeping on jQuery; a bad choice for anything new.
  • If you must vendor the bundle, the extension points are: the Twig function (override via a Twig extension with the same name, or set twig.exception_controller-style overrides — messy), and the config tree (fork it). Archival means no upstream fixes for Twig 3 / Symfony 6+ signatures; expect to patch the getFunctions() return types yourself. That is the real cost of a 4-star archived bundle.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route