Image Optimization (Optional)
Use @sveltejs/enhanced-img for build-time AVIF, WebP, and fallback images. Source files must be available to Vite at build time; this does not cover runtime uploads or remote URLs.
Installation
bash
npm install --save-dev @sveltejs/enhanced-imgVite Configuration
Import enhancedImages and place enhancedImages() before svelte(). Keep the existing plugins, formatting settings, and svelte-check environment guard.
File: vite.config.ts
ts
import inertia from '@inertiajs/vite';
import { wayfinder } from '@laravel/vite-plugin-wayfinder';
import { enhancedImages } from '@sveltejs/enhanced-img';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import tailwindcss from '@tailwindcss/vite';
import laravel from 'laravel-vite-plugin';
import { bunny } from 'laravel-vite-plugin/fonts';
import { defineConfig, lazyPlugins } from 'vite-plus';
const isSvelteCheck = process.argv.some((argument) =>
argument.includes('svelte-check'),
);
if (isSvelteCheck) {
process.env.LARAVEL_BYPASS_ENV_CHECK ??= '1';
}
export default defineConfig({
plugins: lazyPlugins(() => [
laravel({
input: ['resources/css/app.css', 'resources/js/app.ts'],
refresh: true,
fonts: [
bunny('Instrument Sans', {
weights: [400, 500, 600],
}),
],
}),
inertia(),
tailwindcss(),
enhancedImages(),
svelte(),
wayfinder({
formVariants: true,
}),
]),
server: {
watch: {
ignored: [
'**/.agents/**',
'**/.claude/**',
'**/.cursor/**',
'**/.junie/**',
'**/vendor/**',
],
},
},
lint: {
ignorePatterns: [
'vendor/**',
'node_modules/**',
'public/**',
'bootstrap/ssr/**',
'tailwind.config.js',
'resources/js/actions/**',
'resources/js/components/ui/*',
'resources/js/routes/**',
'resources/js/wayfinder/**',
],
options: {
denyWarnings: true,
typeAware: true,
},
},
fmt: {
printWidth: 80,
tabWidth: 4,
singleQuote: true,
semi: true,
singleAttributePerLine: false,
htmlWhitespaceSensitivity: 'css',
ignorePatterns: [
'.agents/skills/**',
'.claude/skills/**',
'.github/**',
'.mcp.json',
'.pi/skills/**',
'AGENTS.md',
'CLAUDE.md',
'boost.json',
'composer.json',
'resources/js/components/ui/*',
'resources/views/mail/*',
],
sortTailwindcss: {
functions: ['clsx', 'cn', 'cva'],
entryPoint: 'resources/css/app.css',
},
},
});Restart the development server after changing the configuration.
TypeScript Declarations
Global Types
Add the @sveltejs/enhanced-img import; keep the existing environment and Inertia declarations.
File: resources/js/types/global.d.ts
ts
import type { Auth } from '@/types/auth';
import '@sveltejs/enhanced-img';
// Extend ImportMeta interface for Vite...
declare module 'vite/client' {
interface ImportMetaEnv {
readonly VITE_APP_NAME: string;
[key: string]: string | boolean | undefined;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
readonly glob: <T>(
pattern: string,
options?: { eager?: boolean },
) => Record<string, T>;
}
}
declare module '@inertiajs/core' {
export interface InertiaConfig {
sharedPageProps: {
name: string;
auth: Auth;
sidebarOpen: boolean;
[key: string]: unknown;
};
}
}Image Imports
Add the *&enhanced declaration for imports with width queries; the package's *?enhanced declaration does not cover that suffix.
File: resources/js/types/images.d.ts
ts
declare module '*&enhanced' {
import type { Picture } from '@sveltejs/enhanced-img';
const image: Picture;
export default image;
}Source Image
bash
mkdir -p resources/imagesSave the original image as resources/images/matrix-rain.jpg. The test expects this 2400 × 1200 fixture and its 2:1 aspect ratio; do not substitute a resized preview.
File: resources/images/matrix-rain.jpg

Import source images through Vite, not a /public URL.
Home Page
Request widths 480, 768, 1280, 1920, and 2400. Keep sizes in sync with the content width and fetchpriority="high" on this hero; do not lazy-load it.
File: resources/js/pages/Home.svelte
svelte
<script module lang="ts">
import { home } from '@/routes';
export const layout = {
breadcrumbs: [
{
title: 'Home',
href: home(),
},
],
};
</script>
<script lang="ts">
import matrixRain from '../../images/matrix-rain.jpg?w=480;768;1280;1920;2400&enhanced';
import AppHead from '@/components/AppHead.svelte';
</script>
<AppHead title="Home" />
<div class="mx-auto w-full max-w-7xl flex-1 px-4 py-20 sm:px-6 lg:px-8">
<h1 class="bg-linear-to-r from-sky-500 to-indigo-500 bg-clip-text text-4xl font-bold tracking-tight text-transparent">
Home
</h1>
<p class="mt-4 max-w-2xl text-lg leading-relaxed text-muted-foreground">
{import.meta.env.VITE_APP_NAME || 'Laravel'} welcomes you.
</p>
<enhanced:img
src={matrixRain}
alt="Glowing green streams of digital code cascading across a dark background"
sizes="(min-width: 1280px) 1216px, (min-width: 1024px) calc(100vw - 64px), (min-width: 640px) calc(100vw - 48px), calc(100vw - 32px)"
fetchpriority="high"
class="mt-10 block h-auto w-full rounded-xl ring-1 ring-emerald-500/20"
/>
</div>Image Optimization Tests
Check output formats, responsive markup, and Laravel dev asset URLs with the real Vite config. Keep the test directly under tests/; it resolves the project root from there.
File: tests/image-optimization.test.mjs
js
import assert from 'node:assert/strict';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { test } from 'node:test';
import { build, createServer } from 'vite-plus';
const root = path.resolve(import.meta.dirname, '..');
await test('builds responsive pictures with the application Vite configuration', async (t) => {
const directory = await mkdtemp(path.join(root, 'tests/.images-'));
t.after(() => rm(directory, { recursive: true, force: true }));
const original = await readFile(
path.join(root, 'resources/images/matrix-rain.jpg'),
);
await writeFile(path.join(directory, 'photo.jpg'), original);
await writeFile(
path.join(directory, 'Image.svelte'),
`<script>
import photo from './photo.jpg?w=800;400&enhanced';
</script>
<enhanced:img src="./photo.jpg?w=800;400" sizes="100vw" alt="Static image" />
<enhanced:img src={photo} sizes="100vw" alt="Imported image" />`,
);
const entry = path.join(directory, 'entry.js');
await writeFile(
entry,
`import { mount } from 'svelte';
import Image from './Image.svelte';
mount(Image, { target: document.body });`,
);
const result = await build({
mode: 'development',
root,
configFile: path.join(root, 'vite.config.ts'),
logLevel: 'error',
build: {
write: false,
minify: false,
assetsInlineLimit: 0,
rolldownOptions: { input: entry },
},
});
const output = result.output;
const code = output
.filter((file) => file.type === 'chunk')
.map((file) => file.code)
.join('\n');
assert.ok(code.includes('<picture>'));
assert.ok(!code.includes('<enhanced:img'));
assert.match(code, /width=.?800/);
assert.match(code, /height=.?400/);
assert.ok(code.includes('400w'));
assert.ok(code.includes('800w'));
assert.ok(code.includes('100vw'));
for (const format of ['avif', 'webp', 'jpg']) {
const images = output.filter(
(file) =>
file.type === 'asset' &&
(file.fileName.endsWith(`.${format}`) ||
(format === 'jpg' && file.fileName.endsWith('.jpeg'))),
);
assert.equal(images.length, 2, `Expected two ${format} sizes`);
for (const image of images) {
assert.ok(image.source.byteLength > 0);
assert.ok(image.source.byteLength < original.byteLength);
}
}
});
await test('transforms the home image with Laravel development asset URLs', async (t) => {
const server = await createServer({
root,
mode: 'development',
configFile: path.join(root, 'vite.config.ts'),
logLevel: 'error',
optimizeDeps: { noDiscovery: true, include: [] },
plugins: [
{
name: 'image-test-no-hot-file',
configResolved(config) {
/** Transforms only: do not register cleanup of the real public/hot file. */
config.plugins.find(
(plugin) => plugin.name === 'laravel',
).configureServer = undefined;
},
},
],
server: {
middlewareMode: true,
watch: null,
hmr: false,
origin: 'http://localhost:5173',
},
});
t.after(() => server.close());
const page = await server.transformRequest(
'/resources/js/pages/Home.svelte',
);
const image = await server.transformRequest(
'/resources/images/matrix-rain.jpg?w=480;768;1280;1920;2400&enhanced',
);
assert.ok(page.code.includes('picture'));
assert.ok(image.code.includes('http://localhost:5173/@imagetools/'));
assert.ok(image.code.includes('480w'));
assert.ok(image.code.includes('2400w'));
});Run the Node test explicitly; Pest and composer ci:check do not pick it up:
bash
node --test tests/image-optimization.test.mjs
npm run types:check
npm run buildCheck the home image at mobile and desktop widths, both with the dev server and with built assets. The first build is slower; later builds use node_modules/.cache/imagetools.
Commit
bash
git add -A
git commit -m '07 - Image Optimization'