Open-source project
spatie/server-side-rendering avatar
spatie/server-side-rendering

spatie/server-side-rendering runs your JavaScript bundle inside a PHP request

Server side rendering JavaScript in a PHP application

634 stars34 forksPHPMIT

At a glance

What is it?
This Spatie package executes a server side JavaScript entry point from PHP and returns the rendered HTML, either through a V8Js extension or by spawning a node process. It is a small, MIT licensed bridge, and the README is unusually honest about the fact that server side rendering a JavaScript app inside PHP is uncharted territory.
Who is it for?
spatie/server-side-rendering suits a team with an existing PHP application and a JavaScript front end that needs server rendered HTML for crawlers, and which benefits from rendering inside the PHP runtime to reuse the session and the state that a separate stateless API would not have.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A thin bridge between a PHP request and a JavaScript bundle

The package does one thing. It takes a JavaScript entry point, runs it on the server, and hands back the HTML that the framework produced. The opening example in the README is the whole API surface.

php
use Spatie\Ssr\Renderer;
use Spatie\Ssr\Engines\V8;

$engine = new V8();

$renderer = new Renderer($engine);

echo $renderer
    ->entry(__DIR__.'/../../public/js/app-server.js')
    ->render();

// <div>My server rendered app!</div>

You construct an engine, hand it to a renderer, point the renderer at your server bundle with the entry method, and call render. The result is the server rendered markup, printed where a normal view would be. The project describes itself as server side rendering JavaScript in your PHP application, and the primary language is PHP. The library works with any JavaScript framework that allows for server side rendering, and it is deliberately framework agnostic, which is why there is a separate package for Laravel users that the README points to instead. The design assumption is stated too, in a single line that tells you the audience: this readme assumes you already have some know-how about building server rendered JavaScript apps.

Two engines, two different operational profiles

An engine executes a JS script on the server, and the library ships with two of them, and the difference between them decides what you have to operate. The V8 engine wraps some V8Js calls, so you need to install a PHP extension for it, and the README says it is a lightweight wrapper around the V8Js class, requiring the v8js extension. The Node engine writes a temporary file with the necessary scripts to render your app and executes it in a node.js process, so you need node.js installed to use it. The V8 path keeps everything in the PHP process, which means no extra process spawn per render and no node runtime on the server, at the cost of a compiled extension in your PHP build, and the V8Js extension has historically been awkward to install on some platforms. The Node path needs no PHP extension, which makes it the easier start, at the cost of a process per render and a node runtime alongside your PHP one. An engine can run a script, or an array of multiple scripts, so the same engine handles a single bundle or several chunks that have to run in order. Neither engine is a general purpose JavaScript runtime that you should point arbitrary code at, because the input is your own bundle and the execution is per request.

The chaining options, and what they do when something fails

The renderer is a fluent chain, and the options are where the interesting behaviour lives. You can chain any amount of options before rendering to control how everything is displayed. The enabled method toggles server side rendering, and when it is disabled the client script and the fallback html are rendered instead. The debug method has a sharper consequence: when debug is enabled, JavaScript errors cause a PHP exception to throw, and without debug mode the client script and the fallback html are rendered instead so the app can be rendered from a clean slate. That default is the single most important thing to understand about this package. In production the failure mode is not an exception, it is a page that falls back to client side rendering, which is exactly the behaviour you would want for availability and exactly the behaviour that would hide a broken server bundle. Turning debug on in development and leaving it off in production is the intended pattern, and it means you need your own alerting to know when the server path is failing.

php
echo $renderer
    ->disabled($disabled)
    ->context('user', $user)
    ->entry(__DIR__.'/../../public/js/app-server.js')
    ->render();

Passing state into the bundle, and environment variables alongside it

Two mechanisms let PHP talk to the JavaScript side, and they solve different problems. The context method passes a context variable to the server script, and the README says this is useful for hydrating your application's state, with context able to contain anything that is json-serializable. Context can be given as key and value parameters or as an array, so a single user object goes in the first form and a batch of values in the second.

php
echo $renderer
    ->entry(__DIR__.'/../../public/js/app-server.js')
    ->context('user', ['name' => 'Sebastian'])
    ->render();

Inside the bundle the context variable is read directly.

js
// app-server.js

store.user = context.user // { name: 'Sebastian' }

// Render the app...

The env method is separate and does something different: environment variables are placed in process.env when the server script executes, and they must be primitive values like numbers, strings or booleans, which is a meaningful restriction since the context is where you would pass structured data. Together these two are how the PHP side hydrates a JavaScript application with data it already has, which is the specific benefit the README claims for rendering inside PHP rather than in a separate service, since a PHP application can reach the session and the state that a SPA consuming a stateless API cannot.

Installing the package, and the decision to keep going

Installation is a single Composer command.

bash
composer require spatie/server-side-rendering

The README's framing around that command is more useful than the command itself. It states that server side rendering can be hard and non-trivial to enable in a JavaScript application, and that before using the library you should make sure you know what you are getting into, with links to articles on the benefits and caveats of SSR and on rendering a Vue application in Laravel, the latter having inspired the library. The refresher list is balanced in a way most package documentation is not. SSR reduces the time until the first meaningful paint, and it is recommended if you need to tailor your app for crawlers that cannot execute JavaScript, but it adds a meaningful amount of complexity, and it can increase response times and the overall load on your server. Then the question that matters for this package: do you need SSR in a PHP application at all. The benefits of rendering in a PHP runtime are access to the application's session and state, which you normally do not have if your SPA is consuming a stateless API, and reduced infrastructure complexity because you do not need to maintain a node server. The closing disclaimer is worth repeating to anyone evaluating it, since the author says that judging by the amount, well the lack, of people blogging about rendering JavaScript applications in PHP, this whole setup is uncharted territory and there may be more unknown caveats lurking around the corner.

Where the PHP runtime actually wins, and where it does not

The honest comparison is with a purpose built SSR framework, and the difference in approach is the whole argument. Next.js and Nuxt.js are built around the idea that rendering happens in a JavaScript runtime, with their own build pipelines, their own routing conventions, their own data loading model and their own deployment story. They are mature, widely deployed, and they assume you are building the application in that framework rather than embedding a framework inside an existing PHP codebase. The PHP approach here inverts that. Your JavaScript bundle is an asset that happens to be executable, your PHP application keeps its own routing, controllers, session and database, and the rendering step is a call in a controller that returns markup. The gain is that you do not run a second application, you do not duplicate session handling, and you do not have to expose your data over an API for your own front end. The cost is that the two ecosystems have to be kept in step manually, that the process boundary is either a PHP extension or a node subprocess, and that the number of people who have done this is small enough that the README itself warns you about it. If your application is a SPA talking to an external API and the PHP side adds nothing, the README's own advice applies and you should not build this.

Editorial conclusion

spatie/server-side-rendering suits a team with an existing PHP application and a JavaScript front end that needs server rendered HTML for crawlers, and which benefits from rendering inside the PHP runtime to reuse the session and the state that a separate stateless API would not have. It is the wrong tool if your front end is already a separate SPA talking to an external API, because the README says plainly that in that case you are probably better off with a battle tested solution like Next.js or Nuxt.js. Before you adopt it, read the fallback and debug behaviour, because the default path is to render the client script and the fallback HTML instead of the server output when anything goes wrong, and decide which engine you will run, since the V8 engine needs the v8js PHP extension while the Node engine needs node.js installed.

Frequently asked questions

How do I install spatie/server-side-rendering?

Through Composer, with composer require spatie/server-side-rendering. The package is also published on Packagist, and if you are building a Laravel app the README points you to the separate laravel-server-side-rendering package instead.

What are the two engines and what do they need?

The V8 engine is a lightweight wrapper around the V8Js class and requires the v8js PHP extension, while the Node engine writes a temporary file with the necessary scripts and executes it in a node.js process, so node.js has to be installed. The library runs with or without the V8Js extension.

What happens when the JavaScript throws during rendering?

With debug enabled, JavaScript errors cause a PHP exception to throw. Without debug mode, the client script and the fallback html are rendered instead, so the app can be rendered from a clean slate. The enabled method works the same way, rendering the client script and fallback html when server side rendering is turned off.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. spatie/server-side-rendering on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/spatie-server-side-rendering.svg)](https://hysenlabs.com/projects/spatie-server-side-rendering)

Was this useful?