Rendering React with PHP, part two: semantic response codes
302
s for redirects, 404
s for inexistant URLs and 500
s for errors.Let's start by modifying our PHP code:
<?php
use Silex\Application;
use Silex\Provider\TwigServiceProvider;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
require_once(__DIR__.'/vendor/autoload.php');
$app = new Application();
$app['v8js'] = new V8Js();
$app->register(new TwigServiceProvider(), ['twig.path' => __DIR__.'/views']);
$app
->get(
'/{uri}',
function ($uri) use ($app) {
// Define content markers for special cases
$redirectPrefix = 'redirect=';
$notFound = 'notfound';
$errorPrefix = 'error=';
$js = implode(';', [
'var uri = \'/'.$uri.'\'',
// Inject them to be able to use them on the JS side
'var redirectPrefix = \''.$redirectPrefix.'\'',
'var notFound = \''.$notFound.'\'',
'var errorPrefix = \''.$errorPrefix.'\'',
file_get_contents(__DIR__.'/dist/app.js')
]);
ob_start();
$app['v8js']->executeString($js);
$content = ob_get_clean();
if (preg_match('/^'.$redirectPrefix.'/', $content)) {
// JS-generated response is "redirect=/new/uri"
return new RedirectResponse(substr($content, strlen($redirectPrefix)));
}
if ($notFound === $content) {
throw new NotFoundHttpException();
}
if (preg_match('/^'.$errorPrefix.'/', $content)) {
// JS-generated response is "error=Some error message"
throw new RuntimeException(substr($content, strlen($errorPrefix)));
}
return $app['twig']->render('app.twig', compact('content'));
}
)
->assert('uri', '.*')
->value('uri', '');
$app->run();
We will now make our JavaScript code craft appropriately shaped responses to make it all work together:
import React from 'react';
import ReactDOM from 'react-dom';
import { renderToString } from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import routes from './routes';
(function() {
'use strict';
if ('undefined' !== typeof document) {
// client-side
// ...
} else {
match(
{
routes,
location: uri
},
function(error, redirectLocation, renderProps) {
print(error
? errorPrefix + JSON.stringify(error)
: redirectLocation
? redirectPrefix + redirectLocation.pathname + redirectLocation.search
: renderProps
? renderToString(<RoutingContext {...renderProps} />)
: notFound
);
}
);
}
})();
And it works! To try it out, you can add a redirect to your routes as follows:
import Root from './components/root';
import HomePage from './components/homePage';
import OtherPage from './components/otherPage';
export default {
path: '/',
component: Root,
indexRoute: {
component: HomePage
},
childRoutes: [
{
path: 'other',
component: OtherPage
},
{
path: 'redirect',
onEnter: function (nextState, replaceState) {
replaceState(null, '/');
}
}
]
};
You obviously also can use react-router's JSX notation, if you prefer to do so.