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

Turbo Laravel Package

rcrowe/turbo

Turbo is a Laravel package that speeds up development by scaffolding common application components. Generate models, migrations, controllers, and views from a single Artisan command, moving from idea to working CRUD with far less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Turbo is a thin Laravel wrapper around jQuery-pjax: it injects the pjax JS into your layout via a Blade directive, and a middleware slices the response HTML down to a single container element whenever the request carries the X-PJAX header. That's the whole package — the value is the automatic fragment extraction, not the client library.

Install and wire it up

composer require rcrowe/turbo
php artisan vendor:publish --provider="RCrowe\Turbo\TurboServiceProvider"   # config/turbo.php

The service provider is auto-discovered on Laravel 5.5+. Register the middleware in the web group in app/Http/Kernel.php:

protected $middlewareGroups = [
    'web' => [
        \RCrowe\Turbo\Middleware\TurboMiddleware::class,
        // ... EncryptCookies, StartSession, etc.
    ],
];

Layout is where you spend your time. Two things must be true on every page pjax can land on:

<head>
    ...
    @turbo   {{-- pulls in jQuery + jquery.pjax --}}
</head>
<body>
    @include('partials.nav')       {{-- OUTSIDE the container --}}

    <div id="pjax-container">      {{-- id must match config/turbo.php --}}
        @yield('content')
    </div>

    <script src="{{ mix('js/app.js') }}"></script>
</body>

Where to look first, in order:

  1. config/turbo.php — container selector, pjax options (timeout, cacheBust, maxCacheLength, fragment/scroll behavior). This is the only file you'll routinely edit.
  2. The service provider — to see exactly how @turbo emits the script tags (asset paths, jQuery source, whether it respects a CDN).
  3. RCrowe\Turbo\Middleware\TurboMiddleware — read handle(); it's ~30 lines and tells you precisely which header/query param it keys off and how it finds the container.

First realistic use case: a content-heavy index → detail navigation (blog, docs, dashboard). Wrap the content area, mark the links, click one. In the network tab you should see the request carry X-PJAX: true (or _pjax=#pjax-container) and the response body be only the inner HTML of #pjax-container. If you see a full HTML document, the middleware isn't in the chain or the selector doesn't match.

Verify without a browser:

curl -s -H "X-PJAX: true" https://app.test/posts/1 | head -40

If that returns <html> instead of your article markup, you have a config/middleware problem, not a JS problem — always debug server-side first.

Implementation Patterns

Container discipline. Every page reachable via pjax must contain the same container element, including error pages, the login page, and redirect targets. Anything that must persist across navigations — nav, flash-message holder, modals, footer scripts — lives outside the container. Anything inside gets replaced wholesale on each request.

Middleware ordering. TurboMiddleware reads and rewrites the response body, so it must run before anything that compresses, minifies, or injects into the body (gzip, laravel-debugbar, HTML minifiers, ETag/Content-Length setters). Put it at the top of the web group. If a package insists on running after it, wrap the response yourself: capture $response = $next($request), run turbo's slicing, then re-apply the outer middleware.

Re-initializing JS on navigation. Scripts inside the container don't re-execute (browsers don't run scripts inserted via innerHTML). Everything that binds to DOM must hook pjax:end:

$(document).on('pjax:end', function () {
    initTooltips();
    initDatepickers();
    refreshCsrfToken();     // see gotchas
    trackPageview(location.href);
});

Useful events: pjax:start, pjax:beforeSend, pjax:success, pjax:complete, pjax:timeout, pjax:popstate, pjax:error.

Forms. pjax submits forms via AJAX if the form is inside the container. Keep the CSRF token in the form (@csrf) — pjax posts it through fine. For 422 validation errors you'll usually want to bypass pjax (data-pjax="false") or return the container fragment with the error bag and let the event handler re-render.

Programmatic navigation and refresh.

$.pjax({ url: '/posts/2', container: '#pjax-container', fragment: '#pjax-container' });
$.pjax.reload('#pjax-container');   // after a delete, to re-pull the list

Progressive enhancement for anything that mutates state (POST, PUT, DELETE via links): pjax follows the response, including redirects. This is convenient but means a 302 to /login will silently swap the login page's fragment into your container. Guard server-side:

public function handle($request, Closure $next)
{
    $response = $next($request);

    if ($request->ajax() && $response->isRedirect()) {
        return response()->json(['redirect' => $response->headers->get('Location')], 200);
    }

    return $response;
}

…or simply add data-pjax="false" to destructive links and let them do a normal full submit.

Interaction with modern frontends. If you run Vue/React/Alpine, the container swap destroys and rebuilds the tree. Alpine can be revived by re-dispatching alpine:init on pjax:end; Vue needs a manual app.mount('#pjax-container') inside the handler. For Livewire/Inertia, prefer disabling pjax on those routes — mixed lifecycles are where this package hurts most.

Testing. Add a feature test that asserts the fragment shape:

$this->get('/posts/1', ['X-PJAX' => 'true'])
     ->assertOk()
     ->assertDontSee('<html', false)
     ->assertSee('<h1>First post</h1>', false);

The reusable half. The middleware (server-side fragment slicing, keyed on a header) is the durable idea; the client library is 2012-era jQuery-pjax. A realistic migration path for a bigger app: keep the middleware pattern, swap @turbo's emitted JS for htmx or Hotwire Turbo, which send HX-Request/Turbo-Frame headers instead. Everything else about the integration (container discipline, pjax:end-style rebinding, redirects) maps one-to-one.

Gotchas and Tips

Headers become wrong the moment you slice. If you replace a full document with a fragment, drop Content-Length, ETag, and Last-Modified, or the browser/CDN will serve a truncated or stale body. Also send Vary: X-PJAX on any response cache — otherwise the cached full page can be handed to a pjax request and vice versa.

Selector mismatch is the #1 support ticket. The container id in config/turbo.php, the id in your layout, and (if you use it) the fragment option must be byte-identical. The middleware returns the entire body when it can't find the container, so "it works, but everything is rendered twice" usually means the container was renamed in one place.

The 650ms timeout silently falls back to a full page load. jquery-pjax cancels and hard-navigates if the response is slow, which makes the app feel like the feature randomly stops working on cold caches. Raise timeout in the config (1000–1500ms is reasonable) and watch for slow queries on pjax'd routes.

Scripts in the fragment don't run. This includes third-party widgets, JSON-LD blocks, and per-page @push('scripts') stacks if your stack sits inside the container. Move the @stack outside the container, or trigger the code from pjax:end.

CSRF token rot. Long-lived tabs keep an old csrf-token meta tag; after session regeneration (login, php artisan session:table cleanup) every subsequent pjax POST 419s. Refresh the meta tag on pjax:end from a header or a small endpoint.

Analytics stop counting. Because the page never reloads, gtag/GA only sees the first hit. Send virtual pageviews from pjax:end. Same for scroll-depth, ad impressions, and error trackers.

Back-button and scroll restoration. pjax has its own in-memory cache (maxCacheLength) and pushes state. If users report seeing stale content after back, either set cacheBust: true, lower maxCacheLength, or $.pjax.reload() on pjax:popstate. Scroll position is restored from the cache entry, so mixed full-page and pjax navigation can jump.

Debugging checklist, in order: (1) does curl -H "X-PJAX: true" return a fragment? (2) does the browser request carry the header? (3) is the container present and identical? (4) is compression/debugbar running after turbo? Nine times out of ten it's (1) or (4).

Escape hatches. @turbo is just a directive emitting script tags — override it with a custom Blade directive pointing at your own pjax build (or a CDN, or a fork), and nothing in the middleware breaks. Per-link opt-out is data-pjax="false"; per-container opt-out is calling $.pjax.defaults.container explicitly rather than relying on config.

Maturity reality check. 87 stars and no recent activity means: no Laravel 11 test matrix, no jQuery-free path, and no maintenance on the bundled pjax. Budget for vendoring the middleware (it's small and self-contained, MIT) rather than depending on the package long-term, and pin your jQuery version deliberately — the bundled pjax is not compatible with jQuery 4's removal of $.isFunction-era APIs.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment