본문으로 건너뛰기
버전: 11.x

Asset Bundling (Vite)

Introduction

Vite는 매우 빠른 개발 환경을 제공하고, 운영 환경 배포를 위한 코드 번들링을 지원하는 최신 프론트엔드 빌드 도구입니다. Laravel로 애플리케이션을 개발할 때는 주로 Vite를 사용해 애플리케이션의 CSS와 자바스크립트 파일을 운영 환경에 적합한 에셋으로 번들링합니다.

Laravel은 공식 Vite 플러그인과 Blade 디렉티브를 제공하여, 개발 및 운영 환경 모두에서 에셋 로딩이 원활히 통합되도록 지원합니다.

혹시 Laravel Mix를 사용하고 계신가요? Vite는 최신 Laravel 설치에서 기존 Laravel Mix를 대체합니다. Mix 관련 문서는 Laravel Mix 웹사이트에서 확인할 수 있습니다. Vite로 전환하고 싶다면 migration guide를 참고하세요.

Choosing Between Vite and Laravel Mix

Vite가 도입되기 전까지, 새로운 Laravel 애플리케이션은 에셋 번들링 시 Mix을 기반으로 하는 webpack를 사용했습니다. Vite는 풍부한 자바스크립트 애플리케이션을 개발할 때 훨씬 빠르고 생산적인 경험을 제공합니다. Inertia와 같은 도구를 사용해 SPA(Single Page Application)을 개발하는 경우, Vite가 최적의 선택입니다.

또한, Vite는 Livewire 등 자바스크립트 "스프링클"이 적용된 전통적인 서버 사이드 렌더링 방식의 애플리케이션에서도 잘 동작합니다. 다만, 운영 환경에 포함되지 않는 파일을 별도로 빌드 디렉터리로 복사하는 기능 등 일부 Laravel Mix의 기능은 지원하지 않습니다.

Migrating Back to Mix

Vite 스캐폴딩을 기반으로 새 Laravel 애플리케이션을 시작했지만, 다시 Laravel Mix와 webpack으로 전환해야 한다면 문제 없습니다. official guide on migrating from Vite to Mix를 참고하세요.

Installation & Setup

아래 내용은 Laravel Vite 플러그인을 수동으로 설치하고 설정하는 방법을 설명합니다. 하지만 Laravel의 starter kits에는 이미 모든 설정이 포함되어 있어 가장 빠르게 Laravel과 Vite를 시작할 수 있는 방법입니다.

Installing Node

Vite와 Laravel 플러그인을 실행하기 전에 Node.js(16 이상)와 NPM이 설치되어 있어야 합니다.

node -v
npm -v

최신 Node와 NPM은 the official Node website에서 제공하는 그래픽 설치 프로그램으로 손쉽게 설치할 수 있습니다. 또는 Laravel Sail을 사용하는 경우, Sail을 통해 Node와 NPM을 실행할 수도 있습니다.

./vendor/bin/sail node -v
./vendor/bin/sail npm -v

Installing Vite and the Laravel Plugin

Laravel을 새로 설치하면 애플리케이션 루트 디렉터리에 package.json 파일이 이미 생성되어 있습니다. 기본 package.json에는 Vite와 Laravel 플러그인을 바로 사용할 수 있도록 필요한 항목이 모두 포함되어 있습니다. NPM을 이용해 프론트엔드 의존성을 설치하세요.

npm install

Configuring Vite

Vite는 프로젝트 루트의 vite.config.js 파일을 통해 설정합니다. 이 파일은 필요에 따라 자유롭게 커스터마이징할 수 있으며, @vitejs/plugin-vue, @vitejs/plugin-react 등 애플리케이션에서 필요한 추가 플러그인도 설치해 사용할 수 있습니다.

Laravel Vite 플러그인에서는 애플리케이션의 진입점을 지정해야 합니다. 진입점은 자바스크립트나 CSS 파일일 수 있고, TypeScript, JSX, TSX, Sass 등 프리프로세서를 사용하는 파일도 가능합니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel([
'resources/css/app.css',
'resources/js/app.js',
]),
],
});

SPA(싱글 페이지 애플리케이션), Inertia를 기반으로 한 애플리케이션 등을 구축하는 경우에는 CSS 진입점을 포함하지 않고 Vite를 사용하는 것이 더 효과적입니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel([
'resources/css/app.css', // [tl! remove]
'resources/js/app.js',
]),
],
});

이 경우 자바스크립트를 통해 CSS를 불러와야 하며, 보통 애플리케이션의 resources/js/app.js 파일 내에서 다음과 같이 CSS를 import합니다.

import './bootstrap';
import '../css/app.css'; // [tl! add]

Laravel Vite 플러그인은 여러 개의 진입점 및 SSR entry points과 같은 고급 설정 옵션도 지원합니다.

Working With a Secure Development Server

로컬 개발 웹서버가 HTTPS로 애플리케이션을 서비스 중인 경우, Vite 개발 서버와의 연결에 문제가 발생할 수 있습니다.

Laravel Herd를 사용하여 사이트를 보안 설정했거나, Laravel Valet를 사용해 secure command로 애플리케이션을 보안 설정한 경우, Laravel Vite 플러그인은 자동으로 생성된 TLS 인증서를 감지해 적용합니다.

만약 애플리케이션 디렉터리 명칭과 다른 호스트로 사이트를 보안 설정했다면, 애플리케이션의 vite.config.js 파일에서 수동으로 호스트를 지정할 수 있습니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
// ...
detectTls: 'my-app.test', // [tl! add]
}),
],
});

다른 웹 서버를 사용하는 경우, 신뢰할 수 있는 인증서를 직접 생성하고, Vite에 수동으로 인증서를 지정해야 합니다.

// ...
import fs from 'fs'; // [tl! add]

const host = 'my-app.test'; // [tl! add]

export default defineConfig({
// ...
server: { // [tl! add]
host, // [tl! add]
hmr: { host }, // [tl! add]
https: { // [tl! add]
key: fs.readFileSync(`/path/to/${host}.key`), // [tl! add]
cert: fs.readFileSync(`/path/to/${host}.crt`), // [tl! add]
}, // [tl! add]
}, // [tl! add]
});

시스템에 신뢰할 수 있는 인증서를 생성하지 못한다면, @vitejs/plugin-basic-ssl plugin을 설치 후 설정할 수 있습니다. 신뢰할 수 없는(자체 서명된) 인증서를 사용하는 경우에는 npm run dev 명령어를 실행할 때 콘솔에 표시되는 "Local" 링크를 클릭해 브라우저에서 인증서 경고를 직접 수락해야 Vite 개발 서버에 접근할 수 있습니다.

Running the Development Server in Sail on WSL2

Windows Subsystem for Linux 2(WSL2) 환경에서 Laravel Sail로 Vite 개발 서버를 실행하는 경우, 브라우저가 개발 서버와 제대로 통신할 수 있도록 vite.config.js에 다음 설정을 추가해야 합니다.

// ...

export default defineConfig({
// ...
server: { // [tl! add:start]
hmr: {
host: 'localhost',
},
}, // [tl! add:end]
});

개발 서버가 실행 중인데도 파일 변경이 브라우저에 반영되지 않는 경우, Vite의 server.watch.usePolling option도 추가로 설정해야 할 수 있습니다.

Loading Your Scripts and Styles

Vite 진입점을 설정했다면, 이제 애플리케이션의 루트 템플릿(<head> 부분)에 @vite() Blade 디렉티브를 추가해 에셋을 불러올 수 있습니다.

<!DOCTYPE html>
<head>
{{-- ... --}}

@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>

자바스크립트에서 CSS를 import하는 경우라면, 자바스크립트 진입점만 지정하면 됩니다.

<!DOCTYPE html>
<head>
{{-- ... --}}

@vite('resources/js/app.js')
</head>

@vite 디렉티브는 Vite 개발 서버를 자동으로 감지하고, Hot Module Replacement(HMR)를 위한 Vite 클라이언트를 삽입합니다. 운영(build) 모드에서는 컴파일되고 버전이 적용된 에셋(및 import된 CSS 포함)을 불러옵니다.

필요하다면, @vite 디렉티브를 호출할 때 컴파일된 에셋의 빌드 경로를 지정할 수도 있습니다.

<!doctype html>
<head>
{{-- Given build path is relative to public path. --}}

@vite('resources/js/app.js', 'vendor/courier/build')
</head>

Inline Assets

에셋의 버전 URL을 링크로 제공하는 대신, 에셋의 실제 내용을 그대로 포함해야 할 때도 있습니다. 예를 들어 HTML 콘텐츠를 PDF 생성기로 전달할 때, 파일 대신 에셋 내용을 직접 페이지에 포함해야 할 수 있습니다. 이때는 Vite 파사드가 제공하는 content 메서드를 활용합니다.

@use('Illuminate\Support\Facades\Vite')

<!doctype html>
<head>
{{-- ... --}}

<style>
{!! Vite::content('resources/css/app.css') !!}
</style>
<script>
{!! Vite::content('resources/js/app.js') !!}
</script>
</head>

Running Vite

Vite를 실행하는 방법은 두 가지입니다. 로컬 개발 중에는 dev 명령어로 개발 서버를 실행할 수 있습니다. 개발 서버는 파일 변경을 자동으로 감지해, 열려있는 브라우저 창에 즉시 반영합니다.

운영 배포용 에셋 번들링 및 버전 적용이 필요하다면 build 명령어를 실행하세요.

# Run the Vite development server...
npm run dev

# Build and version the assets for production...
npm run build

WSL2 환경의 Sail에서 개발 서버를 사용한다면 additional configuration이 필요할 수 있습니다.

Working With JavaScript

Aliases

기본적으로 Laravel 플러그인은 자주 사용하는 디렉터리를 쉽게 import할 수 있도록 다음과 같이 공통 별칭을 제공합니다.

{
'@' => '/resources/js'
}

'@' 별칭은 vite.config.js 파일에서 직접 원하는 값으로 덮어쓸 수 있습니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel(['resources/ts/app.tsx']),
],
resolve: {
alias: {
'@': '/resources/ts',
},
},
});

Vue

Vue 프레임워크로 프론트엔드를 개발하려면 @vitejs/plugin-vue 플러그인을 추가로 설치해야 합니다.

npm install --save-dev @vitejs/plugin-vue

설치 후, 해당 플러그인을 vite.config.js 파일에 포함하세요. Laravel에서 Vue 플러그인을 사용할 땐 몇 가지 추가 옵션을 함께 지정해야 합니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
plugins: [
laravel(['resources/js/app.js']),
vue({
template: {
transformAssetUrls: {
// The Vue plugin will re-write asset URLs, when referenced
// in Single File Components, to point to the Laravel web
// server. Setting this to `null` allows the Laravel plugin
// to instead re-write asset URLs to point to the Vite
// server instead.
base: null,

// The Vue plugin will parse absolute URLs and treat them
// as absolute paths to files on disk. Setting this to
// `false` will leave absolute URLs un-touched so they can
// reference assets in the public directory as expected.
includeAbsolute: false,
},
},
}),
],
});

Laravel의 starter kits에는 이미 Vue 및 Vite 설정이 올바르게 포함되어 있습니다. Laravel, Vue, Vite를 가장 빠르게 시작하려면 Laravel Breeze를 확인해 보세요.

React

React 프레임워크로 프론트엔드를 개발하려면 @vitejs/plugin-react 플러그인을 추가로 설치해야 합니다.

npm install --save-dev @vitejs/plugin-react

설치 후, 해당 플러그인을 vite.config.js 구성 파일에 추가하세요.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [
laravel(['resources/js/app.jsx']),
react(),
],
});

JSX가 포함된 모든 파일의 확장자는 .jsx 또는 .tsx로 지정해야 하며, 진입점 역시 필요하다면 shown above처럼 변경해야 합니다.

또한, 기존의 @vite 디렉티브와 함께 추가로 @viteReactRefresh Blade 디렉티브를 포함해야 합니다.

@viteReactRefresh
@vite('resources/js/app.jsx')

@viteReactRefresh 디렉티브는 반드시 @vite보다 먼저 호출되어야 합니다.

Laravel의 starter kits에는 이미 React 및 Vite 설정이 올바르게 포함되어 있습니다. Laravel, React, Vite를 가장 빠르게 시작하려면 Laravel Breeze를 참고하세요.

Inertia

Laravel Vite 플러그인은 Inertia 페이지 컴포넌트를 쉽게 로드할 수 있도록 resolvePageComponent 함수를 제공합니다. 아래는 Vue 3에서 활용하는 예시이며, React 등 다른 프레임워크에서도 활용 가능합니다.

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

createInertiaApp({
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
});

Inertia에서 Vite의 코드 분할(feature)을 사용하는 경우, asset prefetching 옵션을 함께 설정하는 것이 좋습니다.

Laravel의 starter kits에는 Inertia 및 Vite 설정도 이미 포함되어 있습니다. Laravel, Inertia, Vite를 가장 빠르게 시작하려면 Laravel Breeze를 참고하세요.

URL Processing

Vite를 사용하면서 HTML, CSS, JS 내에서 에셋을 참조할 때 주의해야 할 점이 몇 가지 있습니다. 먼저, 에셋을 절대 경로로 참조하면 Vite는 해당 에셋을 번들에 포함하지 않습니다. 따라서 절대 경로로 참조되는 에셋은 반드시 public 디렉터리에 존재해야 합니다. 특히 dedicated CSS entrypoint을 사용하는 경우, 개발 중 브라우저는 CSS가 호스팅되는 Vite 개발 서버에서 해당 경로의 파일을 찾으려 하므로 절대 경로 사용을 피해야 합니다.

상대 경로로 에셋을 참조할 때는, 참조 위치 기준 상대 경로임을 잊지 마세요. 상대 경로로 참조된 모든 에셋은 Vite가 재작성, 버전 적용, 번들링을 수행합니다.

프로젝트 구조 예시는 다음과 같습니다.

public/
taylor.png
resources/
js/
Pages/
Welcome.vue
images/
abigail.png

아래 예시는 Vite가 상대 URL과 절대 URL을 어떻게 처리하는지 보여줍니다.

<!-- This asset is not handled by Vite and will not be included in the build -->
<img src="/taylor.png">

<!-- This asset will be re-written, versioned, and bundled by Vite -->
<img src="../../images/abigail.png">

Working With Stylesheets

Vite의 CSS 지원에 대해서는 Vite documentation에서 더 자세히 확인할 수 있습니다. Tailwind 등 PostCSS 플러그인을 사용하는 경우, 프로젝트 루트에 postcss.config.js 파일을 생성하면 Vite가 자동으로 적용해줍니다.

export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Laravel의 starter kits에는 이미 Tailwind, PostCSS, Vite 관련 설정이 모두 포함되어 있습니다. 혹시 스타터 키트 없이 Tailwind와 Laravel을 사용하고 싶다면 Tailwind's installation guide for Laravel를 참고하세요.

Working With Blade and Routes

Processing Static Assets With Vite

자바스크립트 또는 CSS에서 에셋을 참조할 경우, Vite는 자동으로 해당 에셋의 번들링과 버전 적용을 처리합니다. Blade 기반 애플리케이션을 빌드할 때도, Blade 템플릿에서만 참조되는 정적 에셋을 Vite가 번들링 및 버전 적용하도록 처리할 수 있습니다.

이를 위해서는 엔트리포인트 파일을 통해 Vite가 해당 에셋을 인식할 수 있도록 import해야 합니다. 예를 들어, resources/images에 저장된 이미지와 resources/fonts에 저장된 폰트를 모두 처리하려면, 다음과 같이 애플리케이션의 resources/js/app.js 엔트리포인트에서 import합니다.

import.meta.glob([
'../images/**',
'../fonts/**',
]);

이렇게 설정하면 npm run build 실행 시 Vite가 이 에셋들을 처리합니다. 이후 Blade 템플릿에서 해당 에셋의 버전 URL이 필요하다면 Vite::asset 메서드를 사용할 수 있습니다.

<img src="{{ Vite::asset('resources/images/logo.png') }}">

Refreshing on Save

Blade를 활용한 전통적인 서버사이드 렌더링 애플리케이션을 개발할 때, Vite는 뷰 파일을 변경하면 브라우저를 자동으로 새로고침해 개발 효율을 높여줍니다. 이 기능을 사용하려면 refresh 옵션을 true로 지정하면 됩니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
// ...
refresh: true,
}),
],
});

refresh 옵션이 true일 때, 아래 디렉터리 내에서 파일을 저장하면 npm run dev 실행 중, 브라우저에서 전체 페이지 새로고침이 자동 동작합니다.

  • app/Livewire/**
  • app/View/Components/**
  • lang/**
  • resources/lang/**
  • resources/views/**
  • routes/**

routes/** 디렉터리를 감시하는 것은 Ziggy를 이용해 프론트엔드에서 라우트 링크를 생성하는 경우에 유용합니다.

기본 감시 경로가 필요에 맞지 않다면, 감시할 경로 목록을 직접 지정할 수도 있습니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
// ...
refresh: ['resources/views/**'],
}),
],
});

내부적으로 Laravel Vite 플러그인은 vite-plugin-full-reload 패키지를 사용하며, 이 패키지의 고급 옵션을 활용해 새로고침 동작을 세부적으로 조정할 수도 있습니다. 예를 들어, 아래처럼 config 정의를 전달할 수 있습니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
// ...
refresh: [{
paths: ['path/to/watch/**'],
config: { delay: 300 }
}],
}),
],
});

Aliases

자바스크립트 애플리케이션에서 자주 사용하는 디렉터리에 create aliases하는 것처럼, Blade 내에서도 Illuminate\Support\Facades\Vite 클래스의 macro 메서드를 이용해 Blade용 별칭을 만들 수 있습니다. 일반적으로 "매크로"는 service providerboot 메서드에서 정의합니다.

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}"));
}

매크로를 정의하면, Blade 템플릿 내에서 이를 사용할 수 있습니다. 예를 들어 위에서 만든 image 매크로를 활용해 resources/images/logo.png에 위치한 에셋을 참조할 수 있습니다.

<img src="{{ Vite::image('logo.png') }}" alt="Laravel Logo">

Asset Prefetching

Vite의 코드 분할 기능을 활용해 SPA를 구축할 때, 각 페이지 이동 시마다 필요한 에셋이 동적으로 불러와집니다. 이러한 동작은 UI 렌더링이 지연되는 문제를 초래할 수 있습니다. 만약 여러분이 사용하는 프론트엔드 프레임워크에서 이 부분이 문제라면, Laravel은 애플리케이션의 JavaScript 및 CSS 에셋을 최초 페이지 로드 시 미리 프리페치(prefetch)할 수 있도록 지원합니다.

Laravel에서 프리페치 기능을 사용하려면, service providerboot 메서드에서 Vite::prefetch 메서드를 호출하면 됩니다.

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Vite::prefetch(concurrency: 3);
}
}

위 예시에서는 각 페이지 로드 시 최대 3개의 에셋이 동시에 프리페치됩니다. 애플리케이션의 필요에 따라 동시 다운로드 개수를 조정할 수 있으며, 제한 없이 모든 에셋을 한 번에 프리페치하고 싶다면 아래와 같이 사용할 수 있습니다.

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Vite::prefetch();
}

기본적으로 프리페치는 page load event가 발생하면 시작됩니다. 프리페치가 시작되는 시점을 커스터마이즈하고 싶다면, Vite가 대기할 이벤트를 직접 지정할 수 있습니다.

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Vite::prefetch(event: 'vite:prefetch');
}

위와 같이 설정하면 여러분이 window 객체에 vite:prefetch 이벤트를 직접 디스패치할 때 프리페치가 시작됩니다. 예를 들어, 페이지가 로드되고 3초 후에 프리페치를 시작하려면 아래와 같은 코드를 사용할 수 있습니다.

<script>
addEventListener('load', () => setTimeout(() => {
dispatchEvent(new Event('vite:prefetch'))
}, 3000))
</script>

Custom Base URLs

만약 사용 중인 Vite로 빌드한 에셋이 애플리케이션과는 다른 도메인(예: CDN)을 통해 배포되는 경우, 애플리케이션의 .env 파일 내에 ASSET_URL 환경 변수를 반드시 지정해야 합니다.

ASSET_URL=https://cdn.example.com

에셋 URL을 설정하면, 모든 재작성된 에셋 경로 앞에 입력한 값이 자동으로 붙게 됩니다.

https://cdn.example.com/build/assets/app.9dce8d17.js

absolute URLs are not re-written by Vite. 따라서 절대 URL에는 프리픽스가 추가되지 않습니다.

Environment Variables

자바스크립트 코드 안에서 환경 변수를 사용하려면, 애플리케이션의 .env 파일에서 환경 변수 이름 앞에 VITE_ 접두어를 붙이면 됩니다.

VITE_SENTRY_DSN_PUBLIC=http://example.com

이렇게 주입된 환경 변수는 import.meta.env 객체를 통해 접근할 수 있습니다.

import.meta.env.VITE_SENTRY_DSN_PUBLIC

Disabling Vite in Tests

Laravel의 Vite 연동 기능은 테스트 실행 시에도 에셋의 경로를 자동으로 해결하려고 시도합니다. 따라서 Vite 개발 서버를 실행하거나, 에셋을 미리 빌드해 두어야 합니다.

테스트 중에 Vite의 동작을 모킹(mock)하고 싶을 경우, Laravel의 TestCase 클래스를 상속하는 테스트에서 withoutVite 메서드를 호출할 수 있습니다.

test('without vite example', function () {
$this->withoutVite();

// ...
});
use Tests\TestCase;

class ExampleTest extends TestCase
{
public function test_without_vite_example(): void
{
$this->withoutVite();

// ...
}
}

모든 테스트에서 Vite를 비활성화하고 싶거나, 기본적으로 해당 동작을 적용하고 싶다면, 베이스 TestCase 클래스의 setUp 메서드에서 withoutVite를 호출하면 됩니다.

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
protected function setUp(): void// [tl! add:start]
{
parent::setUp();

$this->withoutVite();
}// [tl! add:end]
}

Server-Side Rendering (SSR)

Laravel Vite 플러그인을 사용하면 Vite 기반의 서버 사이드 렌더링(SSR) 환경도 손쉽게 구축할 수 있습니다. 먼저 resources/js/ssr.js 위치에 SSR용 엔트리 포인트 파일을 만들고, Laravel 플러그인 옵션의 ssr 키에 해당 경로를 지정하세요.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.js',
ssr: 'resources/js/ssr.js',
}),
],
});

SSR 엔트리 포인트 빌드를 누락하지 않도록 하기 위하여, 애플리케이션의 package.json 파일 내 "build" 스크립트를 다음과 같이 수정하세요.

"scripts": {
"dev": "vite",
"build": "vite build" // [tl! remove]
"build": "vite build && vite build --ssr" // [tl! add]
}

이제 SSR 서버의 빌드 및 실행은 아래 명령어로 수행할 수 있습니다.

npm run build
node bootstrap/ssr/ssr.js

SSR with Inertia을 사용하는 경우, SSR 서버는 inertia:start-ssr 아티즌 명령어로 구동할 수도 있습니다.

php artisan inertia:start-ssr

Laravel의 starter kits에는 이미 Laravel, Inertia SSR, Vite의 적절한 설정이 포함되어 있습니다. Laravel, Inertia SSR, Vite의 가장 빠른 시작 방법으로 Laravel Breeze를 참고하실 수 있습니다.

Script and Style Tag Attributes

Content Security Policy (CSP) Nonce

nonce attribute의 일환으로, script 및 style 태그에 Content Security Policy을 포함시키고 싶다면, 커스텀 middleware에서 useCspNonce 메서드로 nonce 값을 생성 또는 지정할 수 있습니다.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;

class AddContentSecurityPolicyHeaders
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
Vite::useCspNonce();

return $next($request)->withHeaders([
'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'",
]);
}
}

useCspNonce 메서드를 호출하면, Laravel은 자동으로 생성되는 모든 script 및 style 태그에 nonce 속성을 포함시켜줍니다.

이미 할당된 nonce 값을 다른 곳에서도 사용해야 하거나, Laravel Ziggy @route directive에 포함된 starter kits에서 nonce를 명시해야 한다면, cspNonce 메서드를 통해 값을 가져올 수 있습니다.

@routes(nonce: Vite::cspNonce())

미리 가지고 있는 nonce로 Laravel이 해당 값을 사용하도록 지정하려면, useCspNonce 메서드에 nonce를 전달할 수 있습니다:

Vite::useCspNonce($nonce);

Subresource Integrity (SRI)

Vite 매니페스트에 에셋의 integrity 해시가 포함되어 있다면, Laravel은 생성되는 script, style 태그에 자동으로 integrity 속성을 추가하여 Subresource Integrity을 보장합니다. 기본적으로 Vite는 매니페스트에 integrity 해시를 포함하지 않으며, vite-plugin-manifest-sri NPM 플러그인을 설치해 활성화해야 합니다.

npm install --save-dev vite-plugin-manifest-sri

설치 후 vite.config.js 파일에서 아래와 같이 플러그인을 활성화하세요.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import manifestSRI from 'vite-plugin-manifest-sri';// [tl! add]

export default defineConfig({
plugins: [
laravel({
// ...
}),
manifestSRI(),// [tl! add]
],
});

필요하다면, 무결성 해시가 기록된 매니페스트의 키 이름도 변경할 수 있습니다.

use Illuminate\Support\Facades\Vite;

Vite::useIntegrityKey('custom-integrity-key');

자동 감지를 완전히 비활성화하려면 useIntegrityKey 메서드에 false를 전달하세요.

Vite::useIntegrityKey(false);

Arbitrary Attributes

script, style 태그에 data-turbo-track과 같은 속성을 추가해야 한다면, useScriptTagAttributesuseStyleTagAttributes 메서드를 이용해 지정할 수 있습니다. 주로 service provider에서 호출하는 것이 일반적입니다.

use Illuminate\Support\Facades\Vite;

Vite::useScriptTagAttributes([
'data-turbo-track' => 'reload', // Specify a value for the attribute...
'async' => true, // Specify an attribute without a value...
'integrity' => false, // Exclude an attribute that would otherwise be included...
]);

Vite::useStyleTagAttributes([
'data-turbo-track' => 'reload',
]);

상황에 따라 조건부로 속성을 추가해야 한다면 콜백 함수를 넘겨, 자원 경로, URL, 매니페스트 청크, 전체 매니페스트 등의 정보를 활용할 수 있습니다.

use Illuminate\Support\Facades\Vite;

Vite::useScriptTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [
'data-turbo-track' => $src === 'resources/js/app.js' ? 'reload' : false,
]);

Vite::useStyleTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [
'data-turbo-track' => $chunk && $chunk['isEntry'] ? 'reload' : false,
]);

Vite 개발 서버가 실행 중일 때에는 $chunk$manifest 인수에 null이 전달됩니다.

Advanced Customization

기본적으로 Laravel Vite 플러그인은 대부분의 애플리케이션에 적합한 기본값을 사용하지만, 때로는 Vite의 동작을 상세히 조정해야 할 수도 있습니다. 그런 경우, @vite Blade 디렉티브 대신 다음과 같은 메서드와 옵션을 활용해 커스터마이징할 수 있습니다.

<!doctype html>
<head>
{{-- ... --}}

{{
Vite::useHotFile(storage_path('vite.hot')) // Customize the "hot" file...
->useBuildDirectory('bundle') // Customize the build directory...
->useManifestFilename('assets.json') // Customize the manifest filename...
->withEntryPoints(['resources/js/app.js']) // Specify the entry points...
->createAssetPathsUsing(function (string $path, ?bool $secure) { // Customize the backend path generation for built assets...
return "https://cdn.example.com/{$path}";
})
}}
</head>

동일한 설정을 vite.config.js 파일에도 똑같이 반영해야 합니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
hotFile: 'storage/vite.hot', // Customize the "hot" file...
buildDirectory: 'bundle', // Customize the build directory...
input: ['resources/js/app.js'], // Specify the entry points...
}),
],
build: {
manifest: 'assets.json', // Customize the manifest filename...
},
});

Dev Server Cross-Origin Resource Sharing (CORS)

Vite 개발 서버에서 에셋을 가져올 때 브라우저에서 CORS(Cross-Origin Resource Sharing) 오류가 발생한다면, 여러분이 이용 중인 출처(origin)가 개발 서버에 접근할 수 있도록 허용해야 합니다. Laravel 플러그인과 함께 사용할 때는 아래의 오리진이 별도 설정 없이 자동 허용됩니다.

  • ::1
  • 127.0.0.1
  • localhost
  • *.test
  • *.localhost
  • 프로젝트의 .env에 지정된 APP_URL

프로젝트별 맞춤 오리진을 가장 쉽게 허용하는 방법은 애플리케이션의 APP_URL 환경 변수를 실제 브라우저에서 방문하는 주소와 일치시키는 것입니다. 예를 들어, https://my-app.laravel 주소로 접속한다면, .env를 다음과 같이 맞춰야 합니다.

APP_URL=https://my-app.laravel

더 정교하게 여러 오리진을 허용하거나, 커스텀 CORS 정책이 필요하다면 Vite's comprehensive and flexible built-in CORS server configuration을 활용하세요. 예를 들어, 프로젝트의 vite.config.js 파일에서 여러 오리진을 server.cors.origin 옵션에 배열로 지정할 수 있습니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.js',
refresh: true,
}),
],
server: { // [tl! add]
cors: { // [tl! add]
origin: [ // [tl! add]
'https://backend.laravel', // [tl! add]
'http://admin.laravel:8566', // [tl! add]
], // [tl! add]
}, // [tl! add]
}, // [tl! add]
});

특정 최상위 도메인 전체를 허용하기 위해 정규표현식을 사용하는 것도 가능합니다. 예를 들어, *.laravel 도메인에 모두 대응하려면 다음과 같이 코드를 작성할 수 있습니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.js',
refresh: true,
}),
],
server: { // [tl! add]
cors: { // [tl! add]
origin: [ // [tl! add]
// Supports: SCHEME://DOMAIN.laravel[:PORT] [tl! add]
/^https?:\/\/.*\.laravel(:\d+)?$/, //[tl! add]
], // [tl! add]
}, // [tl! add]
}, // [tl! add]
});

Correcting Dev Server URLs

Vite 생태계의 일부 플러그인은 슬래시(/)로 시작하는 URL이 항상 Vite 개발 서버를 가리킨다고 간주합니다. 하지만 Laravel과 통합된 환경에서는 해당 규칙이 항상 성립하지는 않습니다.

예를 들어, vite-imagetools 플러그인은 Vite가 에셋을 서비스할 때 아래와 같이 URL을 출력합니다.

<img src="/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520">

vite-imagetools에서는 URL이 /@imagetools로 시작하면 해당 URL을 Vite가 가로채어 처리한다고 가정합니다. 이런 동작을 기대하는 플러그인을 사용할 경우 직접 URL을 보정해주어야 합니다. 이때 vite.config.jstransformOnServe 옵션을 사용할 수 있습니다.

아래 예시에서는 /@imagetools 경로를 개발 서버의 실제 URL로 치환하는 방식입니다.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { imagetools } from 'vite-imagetools';

export default defineConfig({
plugins: [
laravel({
// ...
transformOnServe: (code, devServerUrl) => code.replaceAll('/@imagetools', devServerUrl+'/@imagetools'),
}),
imagetools(),
],
});

이 설정을 하면 Vite가 에셋을 서비스할 때 아래와 같이 개발 서버의 정확한 URL로 경로가 치환되어 출력됩니다.

- <img src="/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520"><!-- [tl! remove] -->
+ <img src="http://[::1]:5173/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520"><!-- [tl! add] -->