├── .styleci.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src ├── SSR.php └── SidecarGateway.php /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | disabled: 4 | - concat_without_spaces 5 | - not_operator_with_successor_space 6 | - cast_spaces 7 | - trailing_comma_in_multiline_array 8 | - heredoc_to_nowdoc 9 | - phpdoc_summary 10 | 11 | risky: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## Unreleased 3 | 4 | ## v0.0.3 - January 9th, 2022 5 | 6 | ### Added 7 | 8 | - Added the ability to compile Ziggy into the function itself if requested. 9 | 10 | ## v0.0.2 - January 7th, 2022 11 | 12 | ### Fixed 13 | 14 | - fix: syntax error, arguments in wrong order by @deanmcpherson in https://github.com/hammerstonedev/sidecar-inertia/pull/1 15 | 16 | ## v0.0.1 - January 7th, 2022 17 | 18 | First release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hammerstone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sidecar SSR for InertiaJS 2 | 3 | > 🚨 This is currently very much in beta! 4 | 5 | You can see a fully working Jetstream + Inertia + Sidecar demo repo at [hammerstonedev/sidecar-inertia-demo](https://github.com/hammerstonedev/sidecar-inertia-demo). 6 | 7 | ## Overview 8 | 9 | This package provides a Sidecar function to run [Inertia server-side rendering](https://inertiajs.com/server-side-rendering) on AWS Lambda. 10 | 11 | Sidecar packages, deploys, and executes AWS Lambda functions from your Laravel application. 12 | 13 | It works with any Laravel 7 or 8 application, hosted anywhere including your local machine, Vapor, Heroku, a shared virtual server, or any other kind of hosting environment. 14 | 15 | - [Sidecar docs](https://hammerstone.dev/sidecar/docs/main/overview) 16 | - [Sidecar GitHub](https://github.com/hammerstonedev/sidecar) 17 | 18 | ## Enabling SSR 19 | 20 | Following the [official Inertia docs](https://inertiajs.com/server-side-rendering#enabling-ssr) on enabling SSR is a good place to start, but there are a few things you can skip: 21 | 22 | - You do not need to `npm install @inertiajs/server` 23 | - You do not need to `npm install webpack-node-externals` 24 | - Come back here when you get to the "Building your application" section 25 | 26 | Make sure that `inertia/laravel-inertia` is at least version `0.5.1`. 27 | 28 | ## Installation 29 | 30 | To require this package, run the following: 31 | 32 | ```shell 33 | composer require hammerstone/sidecar-inertia 34 | ``` 35 | 36 | This will install Sidecar as well. 37 | 38 | ## Using the Sidecar Gateway 39 | 40 | Update your `AppServiceProvider` to use the `SidecarGateway` as the default Inertia SSR Gateway 41 | 42 | ```php 43 | namespace App\Providers; 44 | 45 | use Hammerstone\Sidecar\Inertia\SidecarGateway; 46 | use Illuminate\Support\ServiceProvider; 47 | use Inertia\Ssr\Gateway; 48 | 49 | class AppServiceProvider extends ServiceProvider 50 | { 51 | public function register() 52 | { 53 | // Use Sidecar to run Inertia SSR. 54 | $this->app->instance(Gateway::class, new SidecarGateway); 55 | } 56 | } 57 | ``` 58 | 59 | ## Updating Configuration 60 | 61 | Update your `config/inertia.php` to include the Sidecar settings 62 | 63 | ```php 64 | [ 68 | 'enabled' => true, 69 | 70 | 'sidecar' => [ 71 | // The Sidecar function that handles the SSR. 72 | 'handler' => \Hammerstone\Sidecar\Inertia\SSR::class, 73 | 74 | // Log some stats on how long each Lambda request takes. 75 | 'timings' => false, 76 | 77 | // Throw exceptions, should they occur. 78 | 'debug' => env('APP_DEBUG', false), 79 | 80 | // Compile Ziggy routes with the Lambda function. 81 | 'ziggy' => false 82 | ], 83 | ], 84 | 85 | // ... 86 | ]; 87 | ``` 88 | 89 | ## Configuring Sidecar 90 | 91 | If you haven't already, you'll need to configure Sidecar. 92 | 93 | Publish the `sidecar.php` configuration file by running 94 | 95 | ```shell 96 | php artisan sidecar:install 97 | ``` 98 | 99 | To configure your Sidecar AWS credentials interactively, you can run 100 | 101 | ```shell 102 | php artisan sidecar:configure 103 | ``` 104 | 105 | The [official Sidecar docs](https://hammerstone.dev/sidecar/docs/main/configuration) go into much further detail. 106 | 107 | Now update your `config/sidecar.php` to include the function shipped with this package. 108 | 109 | ```php 110 | [ 114 | \Hammerstone\Sidecar\Inertia\SSR::class 115 | ], 116 | 117 | // ... 118 | ]; 119 | ``` 120 | 121 | ## Updating Your JavaScript 122 | 123 | > This only covers Vue3, please follow the Inertia docs for Vue2 or React, and please open any issues. 124 | 125 | You'll need to update your `webpack.ssr.mix.js` file. This _should_ work for most cases, but please open any issues for errors you run into. (This is based on the Inertia docs, with slight modifications.) 126 | 127 | ```js 128 | const path = require('path') 129 | const mix = require('laravel-mix') 130 | 131 | mix 132 | .js('resources/js/ssr.js', 'public/js') 133 | .options({ 134 | manifest: false 135 | }) 136 | .vue({ 137 | version: 3, 138 | useVueStyleLoader: true, 139 | options: { 140 | optimizeSSR: true 141 | } 142 | }) 143 | .alias({ 144 | '@': path.resolve('resources/js') 145 | }) 146 | .webpackConfig({ 147 | target: 'node', 148 | externals: { 149 | node: true, 150 | // Sidecar will ship a file called compiledZiggy as a part of 151 | // the package. We don't want webpack to try to inline it 152 | // because it doesn't exist at the time webpack runs. 153 | // './compiledZiggy': 'require("./compiledZiggy")' 154 | }, 155 | resolve: { 156 | alias: { 157 | // Uncomment if you're using Ziggy. 158 | // ziggy: path.resolve('vendor/tightenco/ziggy/src/js'), 159 | }, 160 | }, 161 | }) 162 | ``` 163 | 164 | And update your `resources/js/ssr.js` to look something like this. The specifics may vary based on your application. If you're using [Ziggy](https://github.com/tighten/ziggy), you'll want to uncomment the Ziggy stuff. (This is based on the Inertia docs, with slight modifications.) 165 | 166 | ```js 167 | import {createSSRApp, h} from 'vue' 168 | import {renderToString} from '@vue/server-renderer' 169 | import {createInertiaApp} from '@inertiajs/inertia-vue3' 170 | // import route from 'ziggy'; 171 | 172 | exports.handler = async function (event) { 173 | // This is the file that Sidecar has compiled for us if 174 | // this application uses Ziggy. We import it using 175 | // this syntax since it may not exist at all. 176 | // const compiledZiggy = await import('./compiledZiggy'); 177 | 178 | return await createInertiaApp({ 179 | page: event, 180 | render: renderToString, 181 | resolve: (name) => require(`./Pages/${name}`), 182 | setup({app, props, plugin}) { 183 | // const Ziggy = { 184 | // // Start with the stuff that may be baked into this Lambda. 185 | // ...(compiledZiggy || {}), 186 | // 187 | // // Then if they passed anything to us via the event, 188 | // // overwrite everything that was baked in. 189 | // ...event?.props?.ziggy, 190 | // } 191 | 192 | // Construct a new location, since window.location is not available. 193 | // Ziggy.location = new URL(Ziggy.url) 194 | 195 | return createSSRApp({ 196 | render: () => h(app, props), 197 | }).use(plugin).mixin({ 198 | methods: { 199 | // Use our custom Ziggy object as the config. 200 | // route: (name, params, absolute, config = Ziggy) => route(name, params, absolute, config), 201 | }, 202 | }) 203 | }, 204 | }); 205 | } 206 | ``` 207 | 208 | ## Deploying Your SSR Function 209 | 210 | After you have added the SSR function to your `sidecar.php`, you should run `php artisan sidecar:deploy --activate` to 211 | deploy your function. 212 | 213 | This will compile your JavaScript for you as a `beforeDeployment` hook, so you don't have to worry about remembering to do that first. 214 | 215 | ## Debugging SSR 216 | 217 | It's recommended that you deploy your Sidecar function locally so that you can test SSR more quickly. You can run `php artisan sidecar:deploy --activate` from your local machine and your SSR function will be deployed to Lambda. 218 | 219 | You can also set `ssr.sidecar.debug` to `true` in your `config/inertia.php` file, so that Sidecar will throw exceptions when SSR fails instead of falling back to client-side rendering. This will help you diagnose issues quickly. 220 | 221 | ## Ziggy (Optional) 222 | 223 | If you are using Ziggy, you'll need to pass some Ziggy information along to your Lambda. You can do that by adding the following to your 224 | `HandleInertiaRequests` middleware. 225 | 226 | ```php 227 | class HandleInertiaRequests extends Middleware 228 | { 229 | public function share(Request $request) 230 | { 231 | $ziggy = new Ziggy($group = null, $request->url()); 232 | $ziggy = $ziggy->toArray(); 233 | 234 | // During development, send over the entire Ziggy object, so that 235 | // when routes change we don't have to redeploy. In production, 236 | // only send the current URL, as we will bake the Ziggy config 237 | // into the Lambda SSR package. 238 | $ziggy = app()->environment('production') ? Arr::only($ziggy, 'url') : $ziggy; 239 | 240 | return array_merge(parent::share($request), [ 241 | 'ziggy' => $ziggy 242 | ]); 243 | } 244 | } 245 | ``` 246 | 247 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hammerstone/sidecar-inertia", 3 | "description": "A Laravel package to render Inertia apps on AWS Lambda.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Aaron Francis", 9 | "email": "aaron@hammerstone.dev" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.2|^8.0", 14 | "hammerstone/sidecar": "^0.3.9", 15 | "inertiajs/inertia-laravel": "^0.5.1|^0.6.0", 16 | "illuminate/support": "^8.0|^9.0" 17 | }, 18 | "require-dev": { 19 | "orchestra/testbench": "^5.0|^6.0", 20 | "mockery/mockery": "^1.3.3", 21 | "phpunit/phpunit": "^8.4" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Hammerstone\\Sidecar\\Inertia\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "Hammerstone\\Sidecar\\Inertia\\Tests\\": "tests/" 31 | } 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/SSR.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Hammerstone\Sidecar\Inertia; 7 | 8 | use Hammerstone\Sidecar\LambdaFunction; 9 | use Hammerstone\Sidecar\Package; 10 | use Hammerstone\Sidecar\Sidecar; 11 | use Illuminate\Support\Facades\Config; 12 | use Symfony\Component\Process\Process; 13 | use Tightenco\Ziggy\Ziggy; 14 | 15 | class SSR extends LambdaFunction 16 | { 17 | public function name() 18 | { 19 | return 'Inertia-SSR'; 20 | } 21 | 22 | public function memory() 23 | { 24 | // The more memory you give your function, the faster it will 25 | // run, but the more expensive it will be. You will need to 26 | // find the settings that are right for your application. 27 | return 1024; 28 | } 29 | 30 | public function handler() 31 | { 32 | // This format meets AWS requirements. The compiled SSR file 33 | // is "ssr.js", built by Laravel Mix. The function that 34 | // handles the incoming event is called "handler." 35 | return 'ssr.handler'; 36 | } 37 | 38 | public function package() 39 | { 40 | $package = Package::make()->setBasePath(public_path('js')); 41 | 42 | // Since webpack compiles everything down to a single 43 | // file, it's the only thing we need to ship. 44 | $package->include([ 45 | 'ssr.js' 46 | ]); 47 | 48 | // Include Ziggy configuration, if it is required. 49 | $this->includeZiggy($package); 50 | 51 | return $package; 52 | } 53 | 54 | /** 55 | * @param Package $package 56 | */ 57 | protected function includeZiggy(Package $package) 58 | { 59 | if (!$this->shouldIncludeZiggy()) { 60 | return; 61 | } 62 | 63 | $ziggy = json_encode(new Ziggy); 64 | 65 | Sidecar::log('Adding Ziggy to the package.'); 66 | 67 | // Include a file called "compiledZiggy" that simply exports 68 | // the entire Ziggy PHP object we just serialized. 69 | $package->includeStrings([ 70 | 'compiledZiggy.js' => "module.exports = $ziggy;" 71 | ]); 72 | } 73 | 74 | /** 75 | * @return bool 76 | */ 77 | protected function shouldIncludeZiggy() 78 | { 79 | // They have to turn it on, and the package must be installed. 80 | return Config::get('inertia.ssr.sidecar.ziggy', false) 81 | && class_exists('\\Tightenco\\Ziggy\\Ziggy'); 82 | } 83 | 84 | public function beforeDeployment() 85 | { 86 | Sidecar::log('Executing beforeDeployment hooks'); 87 | 88 | // Compile the SSR bundle before deploying. 89 | $this->compileJavascript(); 90 | } 91 | 92 | protected function compileJavascript() 93 | { 94 | Sidecar::log('Compiling Inertia SSR bundle.'); 95 | 96 | $command = ['npx', 'mix', '--mix-config=webpack.ssr.mix.js']; 97 | 98 | if (Sidecar::getEnvironment() === 'production') { 99 | $command[] = '--production'; 100 | } 101 | 102 | Sidecar::log('Running ' . implode(' ', $command)); 103 | 104 | $process = new Process($command, $cwd = base_path(), $env = []); 105 | 106 | // mustRun will throw an exception if it fails, which is what we want. 107 | $process->setTimeout(60)->disableOutput()->mustRun(); 108 | 109 | Sidecar::log('JavaScript SSR bundle compiled!'); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/SidecarGateway.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Hammerstone\Sidecar\Inertia; 7 | 8 | use Exception; 9 | use Hammerstone\Sidecar\LambdaFunction; 10 | use Illuminate\Support\Facades\Config; 11 | use Illuminate\Support\Facades\Log; 12 | use Inertia\Ssr\Gateway; 13 | use Inertia\Ssr\Response; 14 | use Throwable; 15 | 16 | class SidecarGateway implements Gateway 17 | { 18 | 19 | public function dispatch(array $page): ?Response 20 | { 21 | if (!Config::get('inertia.ssr.enabled', false)) { 22 | return null; 23 | } 24 | 25 | if (!$handler = Config::get('inertia.ssr.sidecar.handler')) { 26 | return null; 27 | } 28 | 29 | try { 30 | return $this->execute($handler, $page); 31 | } catch (Throwable $e) { 32 | if (Config::get('inertia.ssr.sidecar.debug')) { 33 | throw $e; 34 | } 35 | 36 | return null; 37 | } 38 | } 39 | 40 | protected function execute($handler, array $page): ?Response 41 | { 42 | $handler = app($handler); 43 | 44 | if (!$handler instanceof LambdaFunction) { 45 | throw new Exception('The configured Sidecar SSR Handler is not a Sidecar function.'); 46 | } 47 | 48 | $result = $handler::execute($page)->throw(); 49 | 50 | if (Config::get('inertia.ssr.sidecar.timings')) { 51 | Log::info('Sending SSR request to Lambda', $result->report()); 52 | } 53 | 54 | $response = $result->body(); 55 | 56 | return new Response( 57 | implode("\n", $response['head']), 58 | $response['body'] 59 | ); 60 | } 61 | } 62 | --------------------------------------------------------------------------------