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 Js Bundle Laravel Package

tystr/react-js-bundle

tystr/react-js-bundle is a Symfony bundle for integrating React.js into PHP applications. It provides rendering and asset integration helpers to bring React-powered components into existing Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing: this is still a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/react.php. If you're in a Laravel codebase, "getting started" means reading its ~5 source files and re-implementing the pattern (a renderer service plus a Blade directive) rather than installing it. The bundle itself only makes sense inside Symfony 2.7/3.x.

What changed in v0.2.0: rendering logic was refactored so the renderer can talk to an external URL (e.g. a Node.js server) instead of the v8js extension. That is the single most important line in the release notes — it removes the ext-v8js hard wall and, ironically, makes the idea of this bundle portable to Laravel and modern PHP even though the bundle code itself is still stuck in 2016. The v8js path presumably still exists as one transport alongside the new one; confirm in Configuration.php.

If you are on Symfony:

  1. Decide the transport before anything else. External URL = no extension to compile, deploy a Node service. In-process V8Js = php -m | grep v8js, custom Docker build, and versions compatible with this bundle stopped at PHP 7.0. Most teams should take the URL path.
  2. composer require tystr/react-js-bundle — pin to ^0.2 since the transport change is structural.
  3. Register new Tystr\ReactJsBundle\TystrReactJsBundle() in AppKernel.php and import the bundle's config.
  4. Look first at DependencyInjection/Configuration.php. The release notes don't name the new keys, so read the source: there should now be something like a transport/driver selector plus an endpoint URL, alongside the legacy path_to_react-style key and the V8Js constructor options. Confirm exact key names there — don't trust blog posts, they've drifted, and this refactor guarantees any older ones are stale.
  5. Stand up the render server: a small Express (or plain http) service that accepts { component, props }, calls ReactDOMServer.renderToString(React.createElement(registry[component], props)), and returns { html }. Ship it as the target: 'node', libraryTarget: 'commonjs2' build of the same source tree your browser bundle comes from.
  6. First use case: replace an empty <div id="app"></div> in a Twig template with real server-rendered markup so the first paint contains content, then let the client bundle mount over it. Same value proposition as before — the only difference is where the JS runs.

Minimal Twig usage still looks roughly like:

<div id="app">{{ react_render('App', props) }}</div>
<script>window.__PROPS__ = {{ props|json_encode|raw }};</script>

Implementation Patterns

Prefer the external-URL transport almost always. In-process V8Js buys you a sub-millisecond call and costs you a compiled extension, a matched V8 shared library, a custom base image, and no upgrade path past PHP 7.0. A localhost or sidecar Node service over HTTP costs you a round trip and gets you a maintainable, debuggable, upgradeable renderer. Choose V8Js only if you genuinely cannot run a second process.

Batch per request, not per component. With the URL transport the dominant cost is the network hop. One HTTP call carrying all components on the page ([{component, props}, ...] -> [{html}, ...]) beats N calls per widget. With the V8Js transport, the equivalent rule holds: instantiate one renderer per request and reuse it.

Keep props JSON-serializable and identical on both sides. Single source of truth: PHP builds $props, Twig hands it to react_render() and also serializes it into a data-props attribute or inline script. Any divergence produces a hydration mismatch — React silently discards your server markup and you pay the SSR cost for nothing.

Use a component registry, not string-eval'd component code. The server bundle contains a { App: AppComponent, Widget: WidgetComponent } map plus the render bridge. Never pass raw JS through props and never build React.createElement calls from PHP strings — that's injection bugs and unmaintainable templates.

Treat SSR as a progressive enhancement, always. With a network hop there are simply more ways for it to fail, so the fallback matters more, not less:

try {
    $html = $this->renderer->render('App', $props);
} catch (\Throwable $e) {
    $this->logger->warning('SSR failed, falling back to client render', [
        'component' => 'App',
        'transport' => $this->transportName,
        'e' => $e,
    ]);
    $html = '';
}

Catch \Throwable, not \Exception — an unreachable endpoint throws a Guzzle/cURL or connection error shape, and a TypeError from a malformed response is neither a V8JsScriptException nor an HTTP exception.

Build pipeline: two webpack configs from one source tree — browser (target: 'web') and server (target: 'node'). Hash the server bundle filename, point the Node service at the current artifact, and keep the PHP-side config (endpoint URL) and the deployed service in lockstep. Rebuild both bundles together; a client/server API drift shows up as a hydration mismatch, which is silent.

If you're porting the pattern to Laravel: the v0.2.0 transport change makes this materially easier. Bind a ReactRenderer singleton in a service provider that wraps an HTTP client (Http::post($endpoint, ...)) with a timeout, and expose a @react('App', $props) Blade directive. No extension, no php.ini, no custom image. The bundle's contribution is still ~60 lines; the engineering cost now lives in the JS server bundle and the Node service rather than in a compiled extension.

Gotchas and Tips

The ext-v8js wall still exists — it's just optional now. If you take the URL transport you skip it entirely: no matching V8 shared library, no custom Docker build stage, no PHP 7.0 ceiling. Verify your choice explicitly; don't half-configure both transports and discover at deploy time that the default is still V8Js.

It is still unmaintained since March 2016 — v0.2.0 is a rendering refactor and a license file, not a modernization. Expect React 0.14/15-era APIs (React.renderToString), no ReactDOMServer namespacing guarantees, no hydrate(), and Twig 1.x assumptions in the extension's signature. Anything you write against the bundle's own glue is frozen in that era; your Node service and JS bundles can be modern, but the transport contract is old.

The new network hop is your new failure surface. Set explicit connect and read timeouts, use a persistent client (connection pooling / keep-alive), and never let a slow renderer hold an fpm worker indefinitely. A circuit breaker that trips to client-only rendering after repeated failures is cheap insurance. Without timeouts you'll be debugging 502s instead of JS.

Treat the render endpoint as internal infrastructure. Bind it to localhost or a private network, don't expose it publicly, don't send cookies or session data with the request, and make it stateless. Keep the endpoint URL in config — never accept it from user input, or you've built an SSRF vector.

Errors now come in two flavours. V8JsScriptException with a nearly useless stack on the legacy path (log the message and the absolute path of the compiled bundle; the usual culprit is a server bundle touching window, document, localStorage, or a browser-only import). On the URL path you get connection errors, non-2xx statuses, and bodies that aren't the JSON you expected — validate the response shape and log the endpoint, component name, and prop keys (redact values; props frequently carry PII).

Never |raw user data. The renderer returns trusted HTML from your bundle; props interpolated into the page are not. Escape on the Twig/Blade side and serialize props through json_encode, never string concatenation. This is unchanged and unchanged for a reason.

Determinism is mandatory. Date.now(), Math.random(), Intl formatting differences, and unordered object iteration all produce server markup that won't match the client's first render. Inject clocks, seeds, and locale-formatted strings from PHP as props.

Caching strategy differs by transport. The V8Js path needed a V8 snapshot keyed on the bundle hash to avoid recompiling per request. The URL path doesn't — instead cache rendered HTML keyed on bundle hash + props hash, and warm the Node process at boot rather than spawning per request. Either way, key on the bundle hash or you'll serve markup from a previous deploy.

The realistic extension points are the renderer service and the transport. There's no plugin system: to add caching, tracing, or a different engine, decorate/replace the renderer service, and now you can also swap the transport behind it. If neither transport fits — you want streaming SSR, per-request Node context, or a framework-native solution — the sane target architecture is exactly what v0.2.0 gestures at: out-of-process Node over HTTP or stdio. Evaluate this bundle for the idea (v0.2.0 makes the idea better), and still budget for owning the implementation.

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.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics