라라벨12, Inertia.js, React starter kit 배포시 SSR 설정하기
2025-03-16 |

라라벨 12 리액트 스타터 킷에서 서버사이드렌더링을 적용하기 위해서 inertia와 ziggy, 라라벨의 설정들을 변경해주어야합니다.
라라벨12 React Starter kit SSR 설정
라라벨12 react starter kit을 설치하면, ssr.jsx 파일이 있고, package.json에서도 ssr 빌드 명령이 있어서, 기본적으로 ssr 대응이 되는 것처럼 보이지만, 실제로 적용해보면 많은 충돌이 발생한다. 정상적으로 진행이 이뤄진다면 다행이지만, 그렇지 않을 경우엔 몇 가지 추가 설정을 진행해야한다.
.env 파일설정
먼저 env 설정에서 SSR 설정을 true로 변경해주자. 해당 항목이 없어서 직접 추가했다.
SSR=true
app.tsx
위에서 설정해 준 SSR 값을 이용해 조건부로 hydrateRoot로 App을 렌더링 해준다. https://inertiajs.com/server-side-rendering#client-side-hydration 링크에서 더 자세한 내용을 볼 수 있다.
import '../css/app.css';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { createRoot, hydrateRoot } from 'react-dom/client';
import { route as routeFn } from 'ziggy-js';
import { initializeTheme } from './hooks/use-appearance';
declare global {
const route: typeof routeFn;
}
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')),
setup({ el, App, props }) {
if (import.meta.env.SSR) {
hydrateRoot(el, <App {...props} />);
return;
}
const root = createRoot(el);
root.render(<App {...props} />);
},
progress: {
color: '#4B5563',
},
});
// This will set light / dark mode on load...
initializeTheme();
ssr.tsx
ssr.jsx 파일로 생성되어있는 것을 ssr.tsx로 변경해주었다. route is not defined 에러가 발생하기 때문에 global.route 설정을 추가해주었다.
/* prettier-ignore */
import { Page } from '@inertiajs/core';
import { createInertiaApp } from '@inertiajs/react';
import createServer from '@inertiajs/react/server';
import ReactDOMServer from 'react-dom/server';
import { RouteName, route } from 'ziggy-js';
createServer((page: Page) =>
createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: (name) => {
const pages = import.meta.glob('./pages/**/*.tsx', {
eager: true,
});
return pages[`./pages/${name}.tsx`];
},
// prettier-ignore
setup: ({ App, props }) => {
/* eslint-disable */
// @ts-expect-error
global.route<RouteName> = (name, params,absolute) => {
route(name, params, absolute,{
...page.props.ziggy,
location: new URL(page.props.ziggy.location),
});
}
/* eslint-enable */
return <App {...props} />
},
}),
);
HandleInertiaRequests.php
위의 대응을 하고 바로 적용하면 location is not defined 에러가 발생할 수 있다. 라라벨과 react 간에 통신이 이뤄지는 과정에서 ziggy.js 라이브러리에 의존하는데, HandleInertiaRequests.php에서 ziggy 설정을 통해 location을 전달해줘야한다.
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Inspiring;
use Illuminate\Http\Request;
use Inertia\Middleware;
use Tighten\Ziggy\Ziggy;
class HandleInertiaRequests extends Middleware
{
/**
* The root template that's loaded on the first page visit.
*
* @see https://inertiajs.com/server-side-setup#root-template
*
* @var string
*/
protected $rootView = 'app';
/**
* Determines the current asset version.
*
* @see https://inertiajs.com/asset-versioning
*/
public function version(Request $request): ?string
{
return parent::version($request);
}
/**
* Define the props that are shared by default.
*
* @see https://inertiajs.com/shared-data
*
* @return array<string, mixed>
*/
public function share(Request $request): array
{
[$message, $author] = str(Inspiring::quotes()->random())->explode('-');
return [
...parent::share($request),
'ziggy' => fn() => [
...(new Ziggy)->toArray(),
'location' => $request->url(),
],
'name' => config('app.name'),
'quote' => ['message' => trim($message), 'author' => trim($author)],
'auth' => [
'user' => $request->user(),
],
];
}
}
vite.config.js
위에 ssr.jsx 파일을 ssr.tsx로 변경했기 때문에 vite.config.js에서도 수정해줘야한다.
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import laravel from "laravel-vite-plugin"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [
laravel({
input: ["resources/css/app.css", "resources/js/app.tsx"],
ssr: "resources/js/ssr.tsx",
refresh: true,
}),
react(),
tailwindcss(),
],
esbuild: {
jsx: "automatic",
},
})
빌드
모든 과정이 완료되었다면 아래 명령을 순서대로 실행하여 SSR을 적용한다.
npm run build:ssr
php artisan optimize:clear
php artisan optimize
php artisan inertia:start-ssrphp artisan inertia:stop-ssr #SSR중단시 사용
아래 링크들을 통해 더 자세한 배포 옵션들을 살펴볼 수 있다.
https://laravel.com/docs/12.x/starter-kits#inertia-ssr
https://inertiajs.com/server-side-rendering
기타 참고사항
위의 내용까지 수정하면 대략적인 부분에서 큰 에러는 없을 것이다. 하지만 SSR로 변경하는 과정에서 브라우저 API라던지, client 렌더링이 발생하는 코드들은 여전히 OOO is not defined 에러를 발생시킬 것이다.
라라벨12 리액트 스타터킷에서는 shadCN/UI가 기본 탑재되어있다보니, 아래와 같은 부분들이 에러를 발생 시킨다. 변경 전 코드들을 어떻게 변경 후로 바꾸었는지 확인하면 아래와 같다.
// use-appearance.tsx// 변경 전
const prefersDark = () =>
window.matchMedia("(prefers-color-scheme: dark)").matches
// 변경 후
const prefersDark = () =>
typeof window !== "undefined"
? window.matchMedia("(prefers-color-scheme: dark)").matches
: undefined // 변경 전
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
// 변경 후
const mediaQuery =
typeof window !== "undefined"
? window.matchMedia("(prefers-color-scheme: dark)")
: null // 변경 전
return () => mediaQuery.removeEventListener("change", handleSystemThemeChange)
// 변경 후
return () => mediaQuery?.removeEventListener("change", handleSystemThemeChange)
보다시피 window 객체를 이용하는 경우에 에러가 발생한다.
window 객체는 웹 브라우저의 창을 담고 있는 객체다. 서버에서는 웹브라우저라는 것을 인식하지 못한다. 문자 그대로 Server Side에서 Rendering을 진행하기때문에 웹브라우저를 나타내는 window객체를 찾지 못해 window is not defined 에러가 발생한다.
그래서 조건문으로 처리해주거나, useEffect와 같은 렌더링 이후에 처리하는 방식으로 우회하여 처리해주어야한다.
ssr과 ziggy와 laravel 설정은 아래 리포지터리에서 많은 도움을 얻었다. https://github.com/fouteox/pingcrm-react-inertia-laravel