Server-Side rendering is a hot topic for client applications. Unfortunately, this is not an easy task, especially for those without node. JS Environment Development.
I have released two libraries to make PHP rendering possible from the server. Spatie/server-side-rendering and spatie/laravel-server-side-rendering are suitable for laravel applications.
Let's take a closer look at some of the concept of server-side rendering, weigh the pros and cons, and then follow the first rule to create a server-side rendering with PHP.
What is server-side rendering
A single-page application (often also called a SPA) is a client-rendered app. This is an app that runs only on the browser side. If you are using frames, such as React, Vue.js or AngularJS, the client will render your App from the beginning.
The work of the browser
The browser takes several steps before the SPA is launched and ready to be used.
- Download JavaScript Scripts
- Parsing JavaScript Scripts
- Running JavaScript Scripts
- Retrieving data (optional, but common)
- Render the application in the original empty container (first meaningful rendering)
- Ready to finish! (can interact with it)
The user will not see any meaningful content until the browser renders the APP completely (it will take a little time). This creates a noticeable delay until the first meaningful rendering is complete, which affects the user experience.
That's why server-side rendering (commonly referred to as SSR) comes in. The SSR pre-renders the initial application state on the server. Here are the steps that the browser needs to follow after using the server-side rendering:
- Render HTML from the server (first meaningful rendering)
- Download JavaScript Scripts
- Parsing JavaScript Scripts
- Running JavaScript Scripts
- Retrieving data
- To make an existing HTML page interactive
- Ready to finish! (can interact with it)
Because the server provides pre-rendered blocks of HTML, users do not have to wait until everything is complete before they can see meaningful content. Note that although the interaction time is still at the end, the perceived performance is greatly improved.
Benefits of server-side rendering
The main advantage of server-side rendering is the ability to improve the user experience. And, if your site needs to deal with an old reptile that can't execute JavaScript, the SSR will be necessary so that the crawler can index the page after the server renders, rather than an empty document.
How does the server render?
It is important to remember that service-side rendering is not trivial. When your web app is running in both the browser and the server, and your Web app relies on DOM access, you need to make sure that these calls are not triggered on the service side because there is no DOM API available.
Infrastructure complexity
Assuming you've decided to render your app-side applications on the server side, if you're reading this article, you're probably using PHP to build most of your apps (features). However, the server-side rendered SPA needs to run in the node. JS Environment, so a second program will need to be maintained.
You need to build bridges between two applications so they can communicate and share data: an API is required. Building stateless APIs is more difficult than building stateful. You need to be familiar with new concepts, such as JWT or OAUTH-based validation, cors,rest, and adding these to existing applications is important.
There will be some loss, we have established SSR to increase the user experience of WEB applications, but SSR is a cost.
Server-Side rendering trade-offs
There is an extra operation on the server. One is that the server adds load pressure, and the second is a slightly longer page response time. However, since the server is now returning valid content, the second problem is not likely to be affected by the user.
Most of the time you'll use node. js to render your SPA code. If your backend code is not written in Javascript, adding a new node. JS Stack will complicate your program architecture.
To simplify the complexity of the infrastructure, we need to find a way to render the client application as a service side of the existing PHP environment.
Render JavaScript in PHP
Rendering the SPA on the server side requires the following three things to be set:
- An engine that can execute JavaScript
- A script that can render an app on the server
- A script that can render and run the app on the client
SSR Scripts 101
The following example uses the Vue.js. If you are accustomed to using other frameworks (such as React), don't worry, their core ideas are similar, and everything looks so similar.
For the sake of simplicity, we use the classic "Hello World" example.
Here is the code for the program (no SSR):
// app.jsimport Vue from ‘vue‘new Vue({ template: ` <div>Hello, world!</div> `, el: ‘#app‘})
This short code instantiates a Vue component and renders it in a container (the empty ID value app
div
).
If you run this script on the server, it throws an error because no DOM is accessible, and Vue tries to render the app in a nonexistent element.
Refactor the script so that it can run on the server.
// app.jsimport Vue from ‘vue‘export default () => new Vue({ template: ` <div>Hello, world!</div> `})// entry-client.jsimport createApp from ‘./app‘const app = createApp()app.$mount(‘#app‘)
We split the previous code into two parts. app.js
as the factory that creates the application instance, and the second part, that is entry-client.js
, runs in the browser, it uses the factory to create the application instance and mounts it in the DOM.
Now we can create an application that does not have DOM dependencies and can write a second script for the server.
// entry-server.jsimport createApp from ‘./app‘import renderToString from ‘vue-server-renderer/basic‘const app = createApp()renderToString(app, (err, html) => { if (err) { throw new Error(err) } // Dispatch the HTML string to the client...})
We introduced the same application factory, but we used the server-side rendering to render the pure HTML string, which will contain the presentation of the initial state of the application.
We already have two of the three key factors: server-side scripting and client script. Now, let's run it on PHP!
Execute JavaScript
Running JavaScript in PHP, the first choice to think of is v8js. V8js is embedded in the PHP extension of the V8 engine, which allows us to execute JavaScript.
Using V8JS to execute scripts is straightforward. We can use the output buffer in PHP and JavaScript print
to capture the results.
$v8 = new V8Js();ob_start();// $script 包含了我们想执行的脚本内容$v8->executeString($script);echo ob_get_contents();
print(‘<div>Hello, world!</div>‘)
The disadvantage of this approach is that a third-party PHP extension is required, and the extension may be difficult or impossible to install on your system, so it will be better if there are other methods that do not need to be installed.
The different approach is to use node. js to run JavaScript. We can open a Node process that is responsible for running the script and capturing the output.
The components of Symfony Process
are what we want.
use Symfony\Component\Process\Process;// $nodePath 是可执行的 Node.js 的路径// $scriptPath 是想要执行的 JavaScript 脚本的路径new Process([$nodePath, $scriptPath]);echo $process->mustRun()->getOutput();
console.log(‘<div>Hello, world!</div>‘)
Note that (printing) is called instead of in Node console.log
print
.
Let's make it come true!
One of the key concepts of the Spatie/server-side-rendering package is the 引擎
interface. The engine is an abstraction of the above JavaScript execution.
namespace Spatie\Ssr;/** * 创建引擎接口。 */interface Engine{ public function run(string $script): string; public function getDispatchHandler(): string;}
run
The method expects the input of a script (the script content , not a path) and returns the execution result. getDispatchHandler
allows the engine to declare how it expects the script to show the publication. For example, the print
method in V8, or Node console.log
.
The V8JS engine is not very fancy to implement. It is more similar to the validation of our above concepts, with some additional error handling mechanisms.
namespace Spatie\Ssr\Engines;use V8Js;use V8JsException;use Spatie\Ssr\Engine;use Spatie\Ssr\Exceptions\EngineError;/** * 创建一个 V8 类来实现引擎接口类 Engine 。 */class V8 implements Engine。{ /** @var \V8Js */ protected $v8; public function __construct(V8Js $v8) { $this->v8 = $v8; } /** * 打开缓冲区。 * 返回缓冲区存储v8的脚本处理结果。 */ public function run(string $script): string { try { ob_start(); $this->v8->executeString($script); return ob_get_contents(); } catch (V8JsException $exception) { throw EngineError::withException($exception); } finally { ob_end_clean(); } } public function getDispatchHandler(): string { return ‘print‘; }}
Note Here we will V8JsException
re-throw as our EngineError
. This allows us to catch the same exception in any engine line of sight.
The
Node engine is a little more complicated. Unlike V8js,node needs file to execute, not script content. Before executing a server-side script, it needs to be saved to a temporary path.
namespace Spatie\ssr\engines;use spatie\ssr\engine;use Spatie\ssr\exceptions\engineerror;use Symfony\Component\ Process\process;use symfony\component\process\exception\processfailedexception;/** * Create a Node class to implement engine interface class engines. */class Node implements engine{/** @var String */protected $nodePath; /** @var String */protected $tempPath; Public function __construct (string $nodePath, String $tempPath) {$this->nodepath = $nodePath; $this->temppath = $tempPath; Public function Run (string $script): string {//generates a random, unique temporary file path. $tempFilePath = $this->createtempfilepath (); The script content is written in the temporary file. File_put_contents ($tempFilePath, $script); Creates a Process execution temp file. $process = new Process ([$this->nodepath, $tempFilePath]); try {return substr ($process->mustrun ()->getoutput (), 0,-1); } catch (Processfailedexception $exception) {throw engineerror::withexception ($exception); } finally {unlink ($tempFilePath); }} Public Function Getdispatchhandler (): string {return ' Console.log '; } protected function Createtempfilepath (): string {return $this->temppath. ' /'. MD5 (Time ()). JS '; }}
In addition to the temporary path step, the implementation method looks fairly straightforward.
We've created the Engine
interface, and then we need to write the rendered class. The following rendering classes come from the spatie/server-side-rendering extension package, which is the structure of the most basic rendering class.
The only dependency of the rendering class is the Engine
implementation of the interface:
class Renderer{ public function __construct(Engine $engine) { $this->engine = $engine; }}
The rendering method render
will handle the logic of the rendering section, and to execute a JavaScript script file, the following two elements are required:
- Our application script file;
- A distribution method for obtaining parsing-generated HTML;
A simple one is render
as follows:
class Renderer{ public function render(string $entry): string { $serverScript = implode(‘;‘, [ "var dispatch = {$this->engine->getDispatchHandler()}", file_get_contents($entry), ]); return $this->engine->run($serverScript); }}
This method accepts the entry-server.js
file path as a parameter.
We need to distribute the pre-parsed HTML from the script to the PHP environment. dispatch
method returns the Engine
method in the class getDispatchHandler
that dispatch
needs to be run before the server script is loaded.
Remember our server-side portal script? Next we call our method in this script dispatch
:
// entry-server.jsimport app from ‘./app‘import renderToString from ‘vue-server-renderer/basic‘renderToString(app, (err, html) => { if (err) { throw new Error(err) } dispatch(html)})
Vue's application scripts do not require special processing, only the use of file_get_contents
methods to read files.
We have successfully created a PHP SSR. The full renderer in spatie/server-side-rendering is Renderer
a bit different from our implementations, they have higher fault tolerance, and richer functionality like a set of PHP and JavaScript mechanisms for sharing data. If you are interested, it is recommended that you read the source code server-side-rendering.
Think twice.
We understand the pros and cons of server-side rendering, knowing that SSR increases the complexity of the application architecture and infrastructure. If server-side rendering doesn't provide any value to your business, then you probably shouldn't think about him first.
If you really want to start using server-side rendering, read the application's schema first. Most JavaScript frameworks have in-depth guidance on SSR. Vue.js even has a dedicated SSR documentation site that explains the pit of data acquisition and management applications for server-side rendering.
If possible, use an actual, proven solution
There are many proven solutions that provide a good experience in SSR development. For example, if you're building a React app, you can use Next.js, or you'll prefer Vue to Nuxt.js, which is a compelling project.
It's not enough? Try PHP service-Side rendering
You can only manage the complexity of your infrastructure with limited resources. You want to render server-side rendering as part of a large PHP application. You don't want to build and maintain a stateless API. If these reasons match your situation, then using PHP for server-side rendering will be a good solution.
I have published two libraries to support PHP's server-side JavaScript rendering: Spatie/server-side-rendering and spatie/laravel-server-side-rendering for Laravel applications 。 The Laravel custom version is ready for use in nearly 0 configurations in the Laravel application, and the general edition needs to be adjusted to the operating environment. Of course, you can refer to the package Readme file for more information.
If you only want to experience, check out the project from Spatie/laravel-server-side-rendering-examples and refer to the Guide to install it.
If you consider server-side rendering, I hope that this kind of package can help you and look forward to further problem-sharing and feedback via Github!
For more modern PHP knowledge, go to the laravel/php knowledge community
Using PHP to do vue.js SSR service-Side rendering