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

React Bundle Laravel Package

lokalguiden/react-bundle

Lokalguiden React Bundle integrates React into Laravel apps, bundling frontend assets and providing simple helpers to render components. It streamlines setup so you can add interactive UI without complex build configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the framework before you install anything. Despite the marketing copy calling this a "Laravel package," lokalguiden/react-bundle is a Symfony bundle. It ships a Bundle subclass, a DI extension with a Configuration tree, compiler passes, and Twig helpers. There is no ReactBundleServiceProvider, no config/react.php, and nothing in it is reachable from Illuminate\Support\ServiceProvider. composer require lokalguiden/react-bundle will not give you a working integration — you will get a package whose classes are never auto-discovered and whose config is never merged. Plan accordingly: this is a read-and-port assignment, not an install.

Where to look first, in order:

  1. composer.json — check the require block for ext-v8js or a Node dependency, and check for a type: symfony-bundle declaration. This single file tells you whether the SSR path is in-process V8 or a Node transport, and it is the fastest way to know if you have a hard hosting blocker.
  2. The DI extension class (usually DependencyInjection/*Extension.php and Configuration.php) — this is the package's real public API. Every config key you'd want in config/react.php lives here. Read the tree first; it is your feature list.
  3. The renderer class — grep for renderToString, ReactDOMServer, v8js, or V8Js. Usually a few hundred lines and cleanly separable. This is the only part worth extracting.
  4. The Twig/Blade helper — find how it injects props and how it emits the mount <div> plus the manifest-resolved <script> tag. This is the pattern you will reimplement.

Your first use case: render one trivial component (<Greeting name="World" />) to HTML outside of Symfony, from a plain PHP script in a scratch directory. If you cannot get renderToString output from a standalone php hello.php in an hour, stop — the rest of the effort is not justified. If you can, you have validated that the renderer core is portable and the port is real work rather than archaeology.

If you are on Laravel and want the outcome rather than the code, do not start here at all: use inertiajs/inertia-laravel + Inertia's SSR server, or spatie/laravel-server-side-rendering, both of which are maintained and idiomatic to Illuminate. Come back to this repo only if you specifically want to study how a PHP monolith drove a React renderer.

Implementation Patterns

The package's architecture is simple enough to restate in Laravel terms, which is the most useful way to work with it day-to-day.

Pattern 1 — Asset registration becomes a service provider. The Symfony bundle registers React entry points and a manifest path through its extension config. In a Laravel port that is a config/react.php plus a ReactServiceProvider that binds a singleton:

// config/react.php
return [
    'manifest' => public_path('build/manifest.json'),
    'ssr'      => env('REACT_SSR', false),
    'ssr_url'  => env('REACT_SSR_URL', 'http://127.0.0.1:3001/render'),
    'entry'    => 'resources/js/app.jsx',
];

Keep the manifest handling identical to the original: read manifest.json, look up the logical entry name, emit the hashed file. Do not reinvent cache-busting — the bundle's manifest reader is the part most worth copying verbatim.

Pattern 2 — Blade islands, one component per mount point. The bundle's Twig helper ultimately does three things: serialise props to JSON, emit a container with a stable ID, and emit the script tag. Reproduce it as a Blade component so usage stays declarative:

<x-react component="Listing/PriceCard" :props="['price' => $listing->price]" />
class ReactComponent extends Component
{
    public function render() {
        return view('components.react', [
            'id'   => 'react-'.Str::uuid(),
            'json' => json_encode($this->props, JSON_HEX_TAG | JSON_THROW_ON_ERROR),
            'ssr'  => app(SsrRenderer::class)->tryRender($this->component, $this->props),
        ]);
    }
}

Note JSON_HEX_TAG. Any port of this package must escape <, >, &, and quotes inside the props blob or you have an XSS hole on every page with user-supplied data in props. The original likely does this in its Twig helper; verify it before you trust it.

Pattern 3 — SSR as an optional enhancement with mandatory CSR fallback. The single most important workflow pattern in the whole package: server rendering must never be able to fail the response. Wrap it:

public function tryRender(string $component, array $props): ?string
{
    if (! config('react.ssr')) return null;
    try {
        $res = Http::timeout(0.5)->post(config('react.ssr_url'), compact('component', 'props'));
        return $res->successful() ? $res->body() : null;
    } catch (\Throwable $e) {
        report($e);
        return null; // Blade renders the empty container; React hydrates client-side.
    }
}

Then in Blade: output {!! $ssr !!} if present, otherwise leave the container empty. ReactDOM.hydrateRoot handles both cases; a missing SSR body degrades to plain client rendering, not a broken page. This is the integration tip that survives every transport choice.

Pattern 4 — Sidecar over in-process V8. If the original used ext-v8js, do not port that dependency — it is absent from Heroku, Vapor, and most Forge base images, and lags PHP majors. Extract the transport instead: run the bundle's renderer as a small standalone Node service (react-dom/server's renderToString behind an HTTP endpoint accepting {component, props}), and have Laravel Http::post() to it. That is the same separation-of-concerns the bundle's renderer class already implies, with none of the extension risk.

Pattern 5 — Cache on component + props hash. SSR of dynamic listing pages is the case this package exists for, and it is also the case where it is most expensive. Cache the rendered markup under a key of sha1($component . json_encode($props)) with a short TTL. Without it, SSR typically adds 20–60% to p95 on dynamic routes for a payoff that only lands on first paint and crawler hits.

Day-to-day workflow once running: write components as usual, register the entry in the manifest input, and treat the SSR path as invisible. Feature-flag it per route so you can measure crawl rate and paint time before enabling it everywhere.

Gotchas and Tips

The framework mismatch is the gotcha. Everything below is downstream of that. Expect to spend the first day simply confirming nothing auto-wires. Do not file issues asking for Laravel support — the repo has no visible release cadence, no tags, and no dependents; you are the only consumer of any patches you need.

Hydration mismatch is silent and expensive. Server and client must run the same React major and minor, and the props you serialise into the container must byte-match what the client reads. Common causes: Date objects serialised to strings server-side, non-deterministic Math.random()/Date.now() in render, locale formatting differences, and keys derived from array index. Mismatch means React throws away the server markup and re-renders — you pay for SSR and get none of the benefit, with a visible flash. Detect it in CI (compare SSR output against the client-rendered DOM snapshot), not in production.

Version skew across deploy. If the Node sidecar and the client bundle deploy separately, a deploy of one before the other silently breaks every page. Bake the renderer and the client bundle into the same release artifact, or version the renderer endpoint and have Laravel send the expected version.

Props escaping. Repeating because it is the one security-relevant detail: json_encode alone does not make props safe in an HTML attribute or inline script. Use JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT and never interpolate props directly into markup. If you port the Twig helper, port the escaping with it.

Timeouts and memory ceilings are mandatory. A component with an infinite loop or an unbounded fetch takes down the worker. Enforce a hard wall-clock cap (100–500 ms) and, on the sidecar, a per-process memory limit with restart-on-request-count rather than restart-on-healthcheck-only. Laravel's Http::timeout() covers the client side; the sidecar must enforce its own.

Config quirks live in the Symfony tree. Wherever the original's Configuration.php has a default, port that default literally. Manifest path conventions in particular — the bundle may assume an entrypoints.json layout or a legacy manifest.json shape, and guessing wrong produces a confusing "file not found" that points at the wrong directory.

Extension points are few and undocumented. If you need to customise renderer behaviour (custom serialisers, component whitelists), you will be editing the fork directly — there are no events or strategy hooks in a bundle of this size. Design your Laravel port with a SsrRenderer interface behind a container binding so you can swap Node/HTTP/prerender implementations without touching Blade.

Debugging the SSR path: log the component name, props hash, transport latency, and whether rendering fell back to CSR. Without that telemetry, a rendering failure looks identical to a slow page, and you will not find it.

Extension point worth keeping in the port: prerender the top N routes at build time to a static cache and serve the cached markup on first request. It removes cold-start latency and the cache-stampede spike after deploy, and it means the renderer is on the hot path for far fewer requests.

Bottom line for a Laravel developer: you will not be using this package, you will be reading it. Timebox the read to a day, take the manifest reader and the SSR-with-fallback pattern, and build the rest on Inertia SSR or a small Node sidecar you control.

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.
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
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle