Isomorphic routing means that you define your routes (i.e. what middleware to run for which path) only once, using them both on the client, and the server.
For an example of isomorphic routing, see client/my-sites/themes.
In order to enable isomorphic routing for a section, set isomorphic: true
for that section in client/sections.js.
The constraints required for isomorphic routing are:
- In
client/mysection/index.js, export a default function that acceptsrouteras an argument. Instead of defining routes by invokingpage, userouter, e.g.
export default function( router ) {
router( '/themes/:slug/:section?/:site_id?', details, makeLayout );
}The contract is that at the end of each route's middleware chain, context.layout
should contain the React render tree to be rendered, which will be done magically
by either the client or the server render, as appropriate. (This is clearly
different from the previous client-side-only routing approach where you'd have
to render to #primary/#secondary DOM elements.)
To facilitate that, you can (but don't have to) use the makeLayout
generic middleware found in client/controller. So in the above example, the
details middleware will just create an element in context.primary (instead of
rendering it to the #primary DOM element, as previously).
Note that makeLayout cannot produce a logged-in Layout on the server side yet,
as that has a lot of dependencies that aren't ready for server-side rendering.
- Realistically, you will probably need to write separate
index.node.jsandindex.web.jsfiles for the server and client side inside your section, as many components needed on the client side aren't server-side ready yet. For more on that, see Server-side Rendering docs. - Keep in mind that a lot of sections still render directly to the
#primaryand#secondary<div />s. Unfortunately, React cannot handle switching between those sections and a section that renders its entire component tree at once (a single-tree rendered section). For this reason, we have to unmount and re-render component trees when switching between these two types of sections. We do this in apage()handler inclient/boot. You'll have to locate that handler and add your isomorphic section to thesingleTreeSectionswhitelist array. - Behind the scenes, we're using a util that adapts
page.jsstyle middleware to Express', our server router's middleware signatures. We might want to switch to an isomorphic router in the future.
We support error handling middleware on the server side. Among other things, this is so that server-side rendered sections can set an HTTP error status, such as 404 if something isn't found.
An error handling middleware takes three instead of just two arguments, err, context, next.
Invoke it with router at the end of your route definitions:
export function notFoundError( err, context, next ) {
context.layout = (
<ReduxProvider store={ context.store }>
<LayoutLoggedOut primary={ <ThemeNotFoundError /> } />
</ReduxProvider>
);
next( err );
}
export default function( router ) {
router( '/themes/:slug/:section?/:site_id?', details, makeLayout );
router( themeNotFound );
}Note that in notFoundError, err is passed as an argument to next. This is how error middleware chains skip skip regular middlewares. The rendering middleware that is implicitly called on the server after all other middlewares are invoked uses err.status to set the HTTP error status.
On the other hand, an error-handling middleware like themeNotFound will be called if any other middleware before it calls next with an error object:
function details( context, next ) ) {
const theme = fetchThemeSomehow( context.params.slug );
if ( ! theme ) {
const err = {
status: 404,
message: 'Theme Not Found',
context.params.slug
};
return next( err );
}
/* Render theme section */
next();
}
}