Chapter : Build Tool Asset Handling
Build Tool Asset Handling
Modern React development relies on sophisticated build tools that handle asset processing automatically. Understanding how these tools work is essential for optimizing your application’s performance and troubleshooting build issues.
Overview
In this section, you’ll learn:
- How Vite processes assets with native ESM
- Next.js automatic image optimization
- Understanding legacy Create React App patterns
- Configuring custom asset loaders
Vite Asset Handling
Vite has become the default choice for modern React projects due to its speed and simplicity.
Static Asset Imports
import logoUrl from './logo.svg'
import imageUrl from './photo.jpg'
function App() {
return (
<div>
<img src={logoUrl} alt="Logo" />
<img src={imageUrl} alt="Photo" />
</div>
)
}
Vite automatically:
- Hashes filenames for cache busting
- Optimizes image sizes
- Inlines small assets as base64
- Copies larger assets to output directory
Explicit URL Imports
For more control over asset loading:
// Import as URL string
import imageUrl from './image.jpg?url'
// Import as raw string content
import svgContent from './icon.svg?raw'
// Import with worker
import Worker from './worker.js?worker'
Public Directory Assets
Place static assets in the public/ directory for direct URL access:
function App() {
// References public/favicon.ico
return <img src="/favicon.ico" alt="Icon" />
}
Vite Configuration
Customize asset handling in vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
assetsInlineLimit: 4096, // 4kb - inline smaller files
rollupOptions: {
output: {
assetFileNames: 'assets/[name]-[hash][extname]'
}
}
},
assetsInclude: ['**/*.glb', '**/*.hdr'] // Custom asset types
})
Next.js Asset Handling
Next.js provides powerful built-in asset optimization, especially for images.
Next.js Image Component
The next/image component automatically optimizes images:
import Image from 'next/image'
import profilePic from '../public/profile.jpg'
export default function Profile() {
return (
<div>
{/* Local image - dimensions inferred */}
<Image
src={profilePic}
alt="Profile picture"
placeholder="blur" // Automatic blur placeholder
/>
{/* Remote image - dimensions required */}
<Image
src="https://example.com/photo.jpg"
alt="Remote photo"
width={800}
height={600}
loading="lazy"
/>
</div>
)
}
Image Optimization Features
Next.js automatically:
- Generates responsive image sizes
- Converts to modern formats (WebP, AVIF)
- Lazy loads images by default
- Provides blur placeholders
- Serves images via CDN
Static Image Asset Imports
import type { StaticImageData } from 'next/image'
import logo from '../public/logo.svg'
interface Props {
imageSrc: StaticImageData
}
export default function Logo({ imageSrc }: Props) {
return <img src={imageSrc.src} alt="Logo" />
}
Next.js Configuration
Configure asset handling in next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['example.com', 'cdn.example.com'],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60,
},
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack']
})
return config
}
}
module.exports = nextConfig
Legacy: Create React App
While Create React App is no longer recommended for new projects, you may encounter it in existing codebases.
CRA Asset Imports
import logo from './logo.svg'
import './App.css'
function App() {
return (
<div className="App">
<img src={logo} alt="Logo" />
</div>
)
}
Public Folder Usage
function App() {
// References public/images/photo.jpg
return <img src={process.env.PUBLIC_URL + '/images/photo.jpg'} alt="Photo" />
}
Module Path Resolution
// jsconfig.json or tsconfig.json
{
"compilerOptions": {
"baseUrl": "src"
}
}
// Now you can import:
import Button from 'components/Button'
// Instead of:
import Button from '../../../components/Button'
CRA Limitations
- No native TypeScript path aliases
- Limited asset optimization
- Slower build times
- No native SVG component support
- Ejection required for advanced config
Migration Path: For existing CRA projects, consider migrating to Vite or Next.js for better performance and developer experience.
Custom Asset Loaders
SVG as React Components
Using SVGR in Vite:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr'
export default defineConfig({
plugins: [
react(),
svgr({
svgrOptions: {
icon: true, // Add icon props
}
})
]
})
// Use SVG as component
import { ReactComponent as Logo } from './logo.svg'
function App() {
return <Logo className="app-logo" />
}
Font Loading
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
if (assetInfo.name?.endsWith('.woff2')) {
return 'fonts/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
}
}
}
}
})
Custom File Types
// Handle .glb 3D model files
export default defineConfig({
assetsInclude: ['**/*.glb', '**/*.gltf'],
plugins: [
{
name: 'glb-loader',
transform(code, id) {
if (id.endsWith('.glb')) {
// Custom transformation logic
return {
code: `export default ${JSON.stringify(id)}`,
map: null
}
}
}
}
]
})
Build Tool Comparison
| Feature | Vite | Next.js | CRA (Legacy) |
|---|---|---|---|
| Build Speed | ⚡ Fastest | Fast | Slow |
| Image Optimization | Manual | Automatic | Manual |
| Code Splitting | Automatic | Automatic | Manual |
| Asset Hashing | ✅ | ✅ | ✅ |
| SVG Components | Plugin | Built-in | Eject required |
| TypeScript | Native | Native | Supported |
| HMR Speed | Instant | Fast | Slow |
| Bundle Size | Optimal | Optimal | Larger |
Best Practices
1. Choose the Right Tool
// Vite: Great for SPAs and fast development
// Perfect for: Component libraries, admin panels, dashboards
// Next.js: Great for full-stack apps with SSR/SSG
// Perfect for: Marketing sites, e-commerce, blogs
// Avoid: Create React App for new projects
2. Configure Asset Optimization
// Optimize for production
export default defineConfig({
build: {
target: 'es2020',
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // Remove console.logs
drop_debugger: true
}
}
}
})
3. Use TypeScript for Assets
// src/types/assets.d.ts
declare module '*.svg' {
const content: string
export default content
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>
}
declare module '*.jpg' {
const src: string
export default src
}
declare module '*.png' {
const src: string
export default src
}
declare module '*.webp' {
const src: string
export default src
}
4. Environment-Specific Assets
// Use environment variables
const API_URL = import.meta.env.VITE_API_URL
const CDN_URL = import.meta.env.VITE_CDN_URL
function loadImage(path: string) {
return `${CDN_URL}${path}`
}
5. Monitor Bundle Size
# Vite bundle analysis
npm run build -- --report
# Next.js bundle analysis
npm install @next/bundle-analyzer
Troubleshooting
Common Issues
Asset not found:
// ❌ Don't use absolute paths
import image from '/src/assets/image.jpg'
// ✅ Use relative paths
import image from './assets/image.jpg'
// ✅ Or use aliases
import image from '@/assets/image.jpg'
Large bundle size:
// ❌ Importing entire libraries
import _ from 'lodash'
// ✅ Import only what you need
import debounce from 'lodash/debounce'
Images not optimizing:
// Next.js - ensure domains are configured
module.exports = {
images: {
domains: ['your-cdn.com'],
}
}
Summary
Modern build tools handle much of the complexity of asset management automatically, but understanding their behavior is crucial for:
- Optimizing performance: Choosing the right tool and configuration
- Debugging issues: Understanding build output and error messages
- Custom requirements: Extending default behavior for specific needs
- Migration: Moving from legacy tools to modern alternatives
Next Steps:
- Explore Performance-Optimized Image Loading for advanced loading strategies
- Learn about Advanced Asset Strategies for production optimization
Key Takeaways:
- Vite offers the fastest development experience for React SPAs
- Next.js provides automatic image optimization and SSR/SSG capabilities
- Create React App is legacy - migrate to modern tools
- TypeScript integration improves asset handling safety
- Build tool configuration enables custom optimization strategies