Separate marketing + app sites with Laravel Jetstream and Inertia.js
April 15, 2021
If you're building an application with Laravel Jetstream, you may find yourself in the situation where you want
- the entire app part to be owned by Intertia.js
- all of the marketing website as standard Blade views
I find this to be a great developer experience. All the app stuff is totally Vue + Inertia, which is great for the more complicated, interactive bits that your app will likely have, while the marketing site is plain ol' Blade. Easy to update, change, throw away, and rework.
Going down this route, there are just a few things you'll want to look out for.
Layouts
It's best to set up two different Blade layouts, one for the application and one for the marketing views. This way you're not including Inertia on the pages that don't have it, and you're not including your marketing CSS and JavaScript in the Inertia part of your site.
The app layout is just the basic one from Jetstream.
layouts/app.blade.php
<!DOCTYPE html><head> <meta charset="utf-8"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>Dashboard for My Great App</title> <link rel="stylesheet" href="{{ mix('css/app.css') }}"> <script src="{{ mix('js/app.js') }}" defer></script></head><body class="font-sans antialiased"> @inertia </body></html>
And then your marketing layout can be whatever you want, but here's a basic example:
layouts/marketing.blade.php
<!DOCTYPE html><head> <meta charset="utf-8"> <title>Marketing for My Great App</title> <link rel="stylesheet" href="{{ mix('css/marketing.css') }}"> <!-- Just include Alpine, no bundle needed --> <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script></head><body class="font-sans antialiased"> @yield('body') </body></html>
App and Marketing on Different Domains
Even if your entire app is in a single Laravel project, it's still a good idea to separate your app and marketing domains.
This makes it easier in the future if you want to move your marketing site off to a third party application, it avoids any route collisions, and lets you scope your cookies and sessions to just the app.
You can add the domain configuration in the app.php
config/app.php
return [ // ... 'marketing_domain' => env('MARKETING_DOMAIN', 'torchlight.dev'), 'app_domain' => env('APP_DOMAIN', 'app.torchlight.dev'),];
And then use the domain
method to scope your routes:
routes/web.php
Route::domain(config('app.marketing_domain'))->group(function () { // All of your marketing routes...}); Route::domain(config('app.app_domain'))->group(function () { // All of your app routes...});
The Logout Action
The last thing you'll need to customize is the LogoutResponse
that Fortify sends. By default, the response is this:
vendor/laravel/fortify/src/Http/Responses/LogoutResponse.php
class LogoutResponse implements \Laravel\Fortify\Contracts\LogoutResponse{ public function toResponse($request) { return $request->wantsJson() ? new JsonResponse('', 204) : redirect('/'); }}
But now because we're trying to redirect to 1) another domain and 2) a non-inertia endpoint, we'll need to update this.
From the (very good) Inertia docs:
Sometimes it's necessary to redirect to an external website, or even another non-Inertia endpoint in your app, within an Inertia request. This is possible using a server-side initiated window.location visit.
That's exactly our situation!
Fortunately, Fortify makes it very easy to customize all of this stuff without having to totally eject.
We're going to create a LogoutResponse of our own and then register it.
app/Http/Responses/LogoutResponse.php
class LogoutResponse implements \Laravel\Fortify\Contracts\LogoutResponse{ public function toResponse($request) { // Use Intertia::location to redirect to an // "external" URL, i.e. our marketing site. return Inertia::location(route('marketing.welcome')); }}
Now that we have the response that we need, the last thing to do is to register it in the service container. We'll bind it to the LogoutResponseContract
class.
app/Providers/FortifyServiceProvider.php
class FortifyServiceProvider extends ServiceProvider{ /** * Register any application services. * * @return void */ public function register() { $this->app->singleton(LogoutResponseContract::class, LogoutResponse::class); }}
Inertia::location("That's it!")
That's all you need to do to get your Jetstream and marketing sites all set up on different domains! It's only a couple of steps, but it can be a little bit of futzing if you've never done it before. Hopefully this has made it easier.