Skip to main content
Version: 1.0

Server Side Rending

Laravext offers two ways to server side render you application: a javascript runtime and a blade engine based. If for some reason the need arises, you can even use both at the same time for different routes.

To sum up, the javascript runtime is quite similar to how Inertia.js' Server Side Rendering works. You create a ssr.(js|ts|jsx|tsx) file which will be used by a node process to render the page being visited and sending it to the client, where it'll be replaced when the javascript loads up.

The blade engine based is quite similar to how you'd traditionally render a blade view, and the contents are later replaced when the javascript starts running. Although handy, it can become cubersome for complex pages where you'll have to essentially replicate whatever you have for you React or Vue components in blade.

Note: You can also use the loading.html file convention for simpler html loadings while your javascript doesn't kick in.

Blade Engine Based

The blade engine based methods you can use are througly explained in the Tools/Blade Directives/@startNexus and @endNexus, Tools/Blade Directives/@startStrand and @endStrand, Tools/Nexus Response/withViewSkeleton and Tools/Nexus Response/withHtmlSkeleton sections of this documentation.

Javascript Runtime

In order to use the Javascript Runtime mode of SSR, you'll need to modify/create some files.

Vite Configuration

First, you'll have to add the ssrattribute of your laravelplugin configuration to point to the file that will be used to render the page, like ./resources/js/ssr.(js|jsx|ts|tsx).

import { defineConfig, loadEnv } from "vite";
import laravel, { refreshPaths } from "laravel-vite-plugin";

// For React
import react from "@vitejs/plugin-react";

// For Vue
import vue from "@vitejs/plugin-vue";

export default function ({ mode }) {
const env = loadEnv(mode, process.cwd());

const host = env.VITE_APP_ENV == "local" ? env.VITE_APP_DOMAIN : undefined;

return defineConfig({
server: { host },
plugins: [
laravel({
input: [
"resources/css/app.css",
"resources/js/app.(js|jsx|ts|tsx)",
],
refresh: [...refreshPaths, "resources/js/**", "app/**"],

// Add this line below to your configuration, and change the name and extension to match your file, of course
ssr: "resources/js/ssr.(js/jsx/ts/tsx)",
}),

// For React
react(),

// For Vue
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});
}

SSR Entry Point

You'll then need to create a file, like ssr.(js|jsx|ts|tsx) in your resources/js folder (needless to say, you can change the name and extension to whatever you want, as long as it matches the configuration above). This file must contain a serve function that will be called by the node process to render the page. It'll receive an object with the window and cookies objects, and must return a createLaravextSsrApp function, which will be used to render the page.

The window object will contain the __laravext object, which will contain all the attributes mentioned in the Concepts/Laravext Prop section of this doc, such as the file conventions, props, etc. The cookies object will contain the cookies sent by the client, so if you use it for something, like for example to keep track of unauthenticated users' language preference, you can use it here (the examples below show how to use it for internationalization using the i18next/vue-i18n libraries).

The createLaravextSsrApp function will receive an object with the following mandatory attributes (assuming you'll make use of the @nexus and @strand blade directives):

  • nexusResolver: A function that will receive the name of the nexus component and must return the component. You can use the resolveComponent function from the @laravext/react/tools or @laravext/vue3/tools to help you with that.
  • strandsResolver: A function that will receive the name of the strand component and must return the component. You should use the same resolveComponent function as above.
  • laravext: The __laravext object from the window object.
  • document: The document object from the window object.

There're some optional attributes included in the examples below that either commented or have comments explaining what they do. You can use them to set up global variables, internationalization, cookies, etc.

ssr.jsx:

import { createLaravextSsrApp } from "@laravext/react";
import { resolveComponent } from "@laravext/react/tools";
import { serve } from "@laravext/react/server";
import { route } from "../../vendor/tightenco/ziggy/src/js";
import i18n from "i18next";
import { initReactI18next, I18nextProvider } from "react-i18next";
import pt from "../../lang/pt.json";

serve(({ window, cookies }) =>
createLaravextSsrApp({
// This is optional, the default is renderToString from 'react-dom/server',
// but you can use renderToStaticMarkup if you want
// render: renderToString,

nexusResolver: (name) =>
resolveComponent(
`./nexus/${name}`,
import.meta.glob("./nexus/**/*")
),
strandsResolver: (name) =>
resolveComponent(
`./strands/${name}.jsx`,
import.meta.glob("./strands/**/*.jsx")
),

// The beforeSetup function is executed once, before any of the setups.
// You can use this to set up global variables or anything else, such as
// internationalization, cookies, etc.
beforeSetup: ({ laravext }) => {
if (laravext?.page_data?.shared_props?.ziggy) {
global.route = (name, params, absolute) =>
route(name, params, absolute, {
...laravext.page_data.shared_props.ziggy,
url: laravext.page_data.shared_props.ziggy.url,
});

global.Ziggy = laravext.page_data.shared_props.ziggy;
}

let user = laravext.page_data?.shared_props?.auth?.user;

const i18nInstance = i18n.createInstance();
i18nInstance.use(initReactI18next).init({
resources: {
pt: {
translation: pt,
},
},
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
});

i18nInstance.changeLanguage(
user?.locale ?? cookies["locale"] ?? "en"
);

laravext.app.i18n = i18nInstance;
},

// This setup is applied to all components, including nexus and strands
setup: ({ component, laravext }) => {
return (
<I18nextProvider i18n={laravext.app.i18n}>
{component}
</I18nextProvider>
);
},

// This setup is applied to all components, including nexus and strands
// setup: ({ component, laravext }) => {
// return <AnyComponentOrProvider>
// {component}
// </AnyComponentOrProvider>
// },

/// The setupNexus function is applied only to the nexus component, after the 'setup'
// function, unless reverseSetupOrder is true
// setupNexus: ({ nexus, laravext }) => {
// return <AnyComponentOrProvider>{nexus}</AnyComponentOrProvider>;
// },

// The setupStrand function is applied only to the strand components, after the 'setup' function,
// unless reverseSetupOrder is true.
// The 'strandData' parameter is the data passed to the strand component from the blade
// where it was located, if applicable.
// setupStrand: ({strand, laravext, strandData}) => {
// return <AnyComponentOrProvider>{strand}</AnyComponentOrProvider>
// },

// If you want to reverse the order of the setup functions, set this to true
// reverseSetupOrder: true,

// Don't forget to pass the window object to the laravext object
laravext: window.__laravext,
document: window.document,
})
);

Updating you npm script

Modify your package.json file to include the vite build --ssr command, like so:

{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",

// "build": "vite build", // This is how the default build script probably looks like
"build": "vite build && vite build --ssr" // This is the build script with SSR
}
}

Activate Laravext's SSR in the config file

In your ./config/laravext.php file, set the ssr.enabled to true (or add an environment variable to control it, like LARAVEXT_JAVASCRIPT_SERVER_SIDE_RENDERING_ENABLED), and set the ssr.url to the URL where the server side rendering will be done.

// ./config/laravext.php
<?php
return [

// the rest of the laravext config

'ssr' => [

'enabled' => true,

// the rest of the laravext.ssr config

],
];

Starting you entry point

Now all you have to do is run this artisan command to start the node process that will render the page:

php artisan laravext:start-ssr

Make sure you have this command set up in your deployment pipeline, and that you've set it up as a background process with Supervisor or something similar.

Additionally, you should also stop the process in between deployments, by running:

php artisan laravext:stop-ssr

Granular Control

Let's say that for some reason you want to deactivate SSR for some routes, or activate it only for some routes. You can do that by setting the laravext.ssr.enabled to 'only' or 'except', and then setting the laravext.ssr.uris or laravext.ssr.route_names to the URIs or route names that should or shouldn't be SSR'd. Internaly, this is checked using the request()->is(config('laravext.ssr.uris', [])) or request()->routeIs(config('laravext.ssr.route_names', [])), respectively.

A good example of this is when you have a component that "doesn't bahave well" with SSR, and you want to deactivate it for that page. A "real world" example is in the React example project, where the markdown editor for the /new page doesn't work well with SSR, so it's deactivated for that page, and the laravext.ssr.enabled is set to 'except' and the laravext.ssr.uris is set to ['new'].

// ./config/laravext.php

return [
// the rest of the laravext config

'ssr' => [
/**
* You can also set it as 'only' or 'except' to specify the URIs that should(n't) be SSR'd,
* if for some reason you need this kind of control.
*/
'enabled' => 'except',

/**
* The URIs that should/should not be SSR'd. This validation is dependent on the enabled config.
*
* If enabled is set to 'only', the URIs listed here will be the only ones that will be SSR'd.
* If enabled is set to 'except', the URIs listed here will be the ones that won't be SSR'd.
*
* If enabled is set to true, this config will be ignored.
*
* Internally this is checked using the request()->is(config('laravext.ssr.uris', []))
*/
'uris' => [
'new',

// 'example/{uri}/pattern/*',
// 'another-example/{uri}'
],

/**
* The route names that should/should not be SSR'd. This validation is dependent on the enabled config.
*
* If enabled is set to 'only', the route names listed here will be the only ones that will be SSR'd.
* If enabled is set to 'except', the route names listed here will be the ones that won't be SSR'd.
*
* If enabled is set to true, this config will be ignored.
*
* Internally this is checked using the request()->routeIs(config('laravext.ssr.route_names', []))
*/
'route_names' => [
// 'route.name',
// 'route.name_pattern.*'
],
],
];