JavaScript SSR
Instead of rendering views with PHP template engines such as Twig, this module enables server-side JavaScript rendering. PHP handles authorization, authentication, initial state, and API delivery, while JavaScript renders the UI. Only resources with the #[Ssr] attribute are affected, making adoption straightforward within existing projects.
Background and Use Cases
This module was developed to enable JavaScript server-side rendering (SSR) within PHP applications.
Today, JavaScript ecosystem frameworks like Next.js, Nuxt, and Remix provide mature solutions for SSR entirely within JavaScript. For new projects with JavaScript-centric UIs, these frameworks are typically the preferred choice.
This module is suited for the following scenarios:
- Rendering most pages with your existing template engine, while using JS UI only for pages requiring high interactivity
- Adding React or Vue.js UI to specific pages in an existing BEAR.Sunday project
- Maintaining a single PHP application without separating frontend and backend
- Enabling PHP and JS teams to develop in parallel using
state/metasas the contract
Only resources with the #[Ssr] attribute are rendered with JS UI, allowing easy coexistence with traditional template engines.
Prerequisites
Note: If you do not install V8Js then JS will be run using Node.js.
JavaScript
Installation
Install bear/ssr-module into the project.
// composer create-project bear/skeleton MyVendor.MyProject && cd MyVendor.MyProject;
composer require bear/ssr-module
Install the UI skeleton app koriym/js-ui-skeleton.
composer require koriym/js-ui-skeleton 1.x-dev
cp -r vendor/koriym/js-ui-skeleton/ui .
cp -r vendor/koriym/js-ui-skeleton/package.json .
npm install
Running the UI application
Lets start by running the demo application. From the displayed web page lets select the rendering engine and run the JS application.
npm run ui
This applications inputs can be set using the ui/dev/config/ config files.
<?php
$app = 'index'; // =index.bundle.js
$state = [ // Application state
'hello' =>['name' => 'World']
];
$metas = [ // value used in SSR only
'title' =>'page-title'
];
return [$app, $state, $metas];
Lets copy the configuration file and try changing the input values.
cp ui/dev/config/index.php ui/dev/config/myapp.php
Reload the browser and try out the new settings.
In this way without changing the JavaScript or core PHP application we can alter the UI data and check that it is working.
The PHP configuration files that have been edited in this section are only used when executing npm run ui.
All the PHP side needs is the output bundled JS file.
Creating the UI application.
Using the variables that have been passed in from PHP, create a render function that returns a rendered string.
const render = (state, metas) => (
__AWESOME_UI__ // Using a SSR compatible library or JS template engine return an output string.
)
The state value is needed in the document root, metas contains other variables, such as those needed in <head>. The render function name cannot be changed.
Here we can grab the name and create a greeting string to be returned.
const render = state => (
`Hello ${state.name}`
)
Save this as ui/src/page/hello/server.js and register this as a Webpack entry point in ui/entry.js.
module.exports = {
hello: 'src/page/hello/server',
};
Having done this a hello.bundle.js bundled file is created for us.
Create a file at ui/dev/config/myapp.php to test run this application.
<?php
$app = 'hello';
$state = ['name' => 'World'];
$metas = [];
return [$app, $state, $metas];
Thats it! Reload the browser to try it out.
Inside the render function you can use any UI framework such as React or Vue.js to create a rich UI.
In a regular application in order to limit the number of dependencies in the server.js entry file import the render module as below.
import render from './render';
global.render = render;
Thus far there has been nothing happening on the PHP side. Development on the SSR application and PHP development can done independently.
PHP
Module Installation
Install SsrModule in AppModule.
<?php
use BEAR\SsrModule\SsrModule;
class AppModule extends AbstractAppModule
{
protected function configure()
{
// ...
$build = dirname(__DIR__, 2) . '/var/www/build';
$this->install(new SsrModule($build));
}
}
The $build directory is where the JS files live.(The Webpack output location set in ui/ui.config.js)
#[Ssr] Attribute
Annotate the resource function to be SSR’d with #[Ssr]. The JS application name is required in app.
<?php
namespace MyVendor\MyRedux\Resource\Page;
use BEAR\Resource\ResourceObject;
use BEAR\SsrModule\Annotation\Ssr;
class Index extends ResourceObject
{
#[Ssr(app: 'index_ssr')]
public function onGet(string $name = 'BEAR.Sunday'): static
{
$this->body = [
'hello' => ['name' => $name]
];
return $this;
}
}
When you want to pass in distinct values for SSR and CSR set a key in state and metas.
#[Ssr(app: 'index_ssr', state: ['name', 'age'], metas: ['title'])]
public function onGet(): static
{
$this->body = [
'name' => 'World',
'age' => 4.6E8,
'title' => 'Age of the World'
];
return $this;
}
To see exactly how you pass in state and metas to achieve SSR see the sample application ui/src/page/index/server. The only influence is from the annotated method, the rest comes straight from the API or HTML rendering configuration.
Runtime PHP Application Settings
Edit ui/ui.config.js, set the Webpack build location in build and web directory in public. The build directory is the same that you set in the SsrModule installation.
const path = require('path');
module.exports = {
public: path.join(__dirname, '../var/www'),
build: path.join(__dirname, '../var/www/build')
};
Running the PHP application
npm run dev
Run using live updating.
When the PHP file is changed it will be automatically reloaded, if there is a change in a React component without hitting refresh the component will update. If you want to run the app without live updating you can by running npm run start.
For other commands such lint or test etc. please see commands.
Performance
The V8 snapshot can be saved to APCu for improved performance. Install ApcSsrModule in ProdModule. V8Js is required:
$bundleSrcBasePath = dirname(__DIR__, 2) . '/var/www/build';
$this->install(new ApcSsrModule($bundleSrcBasePath));
The $bundleSrcBasePath is the directory path where the JavaScript bundle files are located.