Every application needs static assets—images, fonts, documents, icons, and more. Next.js provides the /public folder for static files that are served directly without processing. Understanding when to use public vs imports, how to organize assets efficiently, and best practices for different file types will keep your project maintainable and performant. Let's master static asset management!
The Public Folder
What is the Public Folder?
The /public folder is for static assets served at the root URL path:
Public Folder Characteristics
- Location: At project root (not in /app)
- Served at: Root path (/)
- No processing: Files served as-is
- Build time: Copied to build output
- Caching: Efficiently cached by CDNs
Basic Usage
// File structure:
// /public/images/logo.png
// /public/documents/guide.pdf
// /public/favicon.ico
// Referencing in code:
import Image from 'next/image';
export function Logo() {
return (
<Image
src="/images/logo.png"
alt="Logo"
width={200}
height={50}
/>
);
}
// Download link
export function DownloadLink() {
return (
<a href="/documents/guide.pdf" download>
Download Guide
</a>
);
}
// Favicon (automatic)
// Next.js automatically serves /public/favicon.ico
// ✅ Reference with / (root path)
// ✅ Omit /public in URLs
// ✅ Direct access from browser⚠️ Important: Start Paths with /
Always reference public files starting with /. Use /images/logo.png, not images/logo.png or public/images/logo.png.
Organizing Static Assets
Recommended Folder Structure
public/
├── images/ # Images
│ ├── logo.svg
│ ├── hero.jpg
│ ├── products/ # Product images
│ │ ├── product-1.jpg
│ │ └── product-2.jpg
│ └── avatars/ # User avatars
│ └── default.png
├── icons/ # Icons and favicons
│ ├── favicon.ico
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ └── android-chrome-512x512.png
├── fonts/ # Custom fonts (if not using next/font)
│ ├── CustomFont.woff2
│ └── CustomFont.woff
├── documents/ # PDFs and documents
│ ├── terms.pdf
│ ├── privacy.pdf
│ └── user-manual.pdf
├── videos/ # Video files
│ └── intro.mp4
├── audio/ # Audio files
│ └── notification.mp3
├── data/ # Static JSON data
│ └── products.json
├── robots.txt # SEO robots file
├── sitemap.xml # Sitemap
└── manifest.json # PWA manifest
// ✅ Organized by file type
// ✅ Nested folders for categories
// ✅ Clear naming convention
// ✅ Easy to find and maintainAccessing Nested Assets
import Image from 'next/image';
// Product image in nested folder
export function ProductImage({ id }: { id: string }) {
return (
<Image
src={`/images/products/product-${id}.jpg`}
alt={`Product ${id}`}
width={600}
height={600}
/>
);
}
// Avatar from nested folder
export function UserAvatar({ username }: { username: string }) {
return (
<Image
src={`/images/avatars/${username}.jpg`}
alt={username}
width={80}
height={80}
className="rounded-full"
/>
);
}
// Document download
export function DocumentLink() {
return (
<a href="/documents/terms.pdf" target="_blank" rel="noopener">
View Terms of Service
</a>
);
}
// ✅ Use template literals for dynamic paths
// ✅ Nested folders keep assets organized
// ✅ Clear path structureImporting vs Public Folder
When to Import Images
import Image from 'next/image';
import logoImage from '../assets/logo.png';
export function Header() {
return (
<Image
src={logoImage}
alt="Company Logo"
placeholder="blur"
// Blur placeholder automatic for imports!
/>
);
}
// Benefits of importing:
// ✅ Automatic blur placeholder generation
// ✅ Build-time optimization
// ✅ Width/height automatic from file
// ✅ Type safety (TypeScript knows file exists)
// ✅ Webpack/bundler can optimize
// Use imports for:
// ✅ Critical images (hero, logo)
// ✅ Images with known paths at build time
// ✅ Images needing blur placeholdersWhen to Use Public Folder
import Image from 'next/image';
export function DynamicProductImage({ productId }: { productId: string }) {
return (
<Image
src={`/images/products/${productId}.jpg`}
alt="Product"
width={600}
height={600}
/>
);
}
// Benefits of public folder:
// ✅ Dynamic paths at runtime
// ✅ No build processing needed
// ✅ Direct CDN serving
// ✅ Can add files after build
// ✅ Works with user uploads
// Use public folder for:
// ✅ Dynamic image paths
// ✅ Large number of images
// ✅ User-uploaded content
// ✅ Files that change frequently
// ✅ Non-image assets (PDFs, videos, etc.)Comparison Table
| Feature | Import | Public Folder |
|---|---|---|
| Blur Placeholder | ✅ Automatic | ❌ Manual |
| Dynamic Paths | ❌ No | ✅ Yes |
| Type Safety | ✅ Yes | ❌ No |
| Auto Width/Height | ✅ Yes | ❌ Manual |
| CDN Friendly | ⚠️ OK | ✅ Excellent |
| Add After Build | ❌ No | ✅ Yes |
Common Asset Types
1. Favicon and Icons
public/
├── favicon.ico # Browser favicon (16x16, 32x32)
├── apple-touch-icon.png # Apple touch icon (180x180)
├── icon-192.png # PWA icon (192x192)
├── icon-512.png # PWA icon (512x512)
└── manifest.json # PWA manifest
// Generate at https://realfavicongenerator.net/export const metadata = {
icons: {
icon: '/favicon.ico',
apple: '/apple-touch-icon.png',
},
};
// Or manually in HTML:
<head>
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
</head>
// ✅ Next.js automatically serves /favicon.ico
// ✅ Place in /public root
// ✅ Generate multiple sizes2. Documents (PDFs)
// Store in /public/documents/
export function DocumentLinks() {
return (
<div className="space-y-4">
{/* Open in new tab */}
href="/documents/terms.pdf"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
Terms of Service
</a>
{/* Download directly */}
href="/documents/guide.pdf"
download="user-guide.pdf"
className="text-blue-600 hover:underline"
>
Download User Guide
</a>
{/* Embed PDF */}
<iframe
src="/documents/embedded.pdf"
width="100%"
height="600px"
className="border rounded-lg"
/>
</div>
);
}
// ✅ Direct links to PDFs
// ✅ download attribute for downloads
// ✅ target="_blank" to open in new tab3. Videos and Audio
// Store in /public/videos/ and /public/audio/
export function MediaExamples() {
return (
<div className="space-y-8">
{/* Video */}
<video
controls
width="100%"
poster="/images/video-poster.jpg"
className="rounded-lg"
>
<source src="/videos/intro.mp4" type="video/mp4" />
<source src="/videos/intro.webm" type="video/webm" />
Your browser doesn't support video.
</video>
{/* Audio */}
<audio controls className="w-full">
<source src="/audio/podcast.mp3" type="audio/mpeg" />
<source src="/audio/podcast.ogg" type="audio/ogg" />
Your browser doesn't support audio.
</audio>
{/* Background video */}
<video
autoPlay
muted
loop
playsInline
className="absolute inset-0 w-full h-full object-cover"
>
<source src="/videos/background.mp4" type="video/mp4" />
</video>
</div>
);
}
// ✅ Multiple formats for compatibility
// ✅ poster for video preview
// ✅ controls for user control
// ✅ autoPlay, muted, loop for backgrounds4. Data Files (JSON, CSV)
// Store in /public/data/
export async function getStaticData() {
const response = await fetch('/data/products.json');
const products = await response.json();
return products;
}
// Or fetch in component
export function DataComponent() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('/data/products.json')
.then(res => res.json())
.then(setData);
}, []);
return <div>{/* Use data */}</div>;
}
// ✅ Static data files
// ✅ Fetch like any URL
// ✅ Useful for mock data, configs
// ⚠️ For large data or sensitive data:
// Use API routes instead of public folder5. SEO Files
# robots.txt
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /api/
Sitemap: https://example.com/sitemap.xml<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2024-01-01</lastmod>
<priority>1.0</priority>
</url>
<url>
<loc>https://example.com/about</loc>
<lastmod>2024-01-01</lastmod>
<priority>0.8</priority>
</url>
</urlset>// Or generate sitemap dynamically
// app/sitemap.ts
export default function sitemap() {
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://example.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
];
}
// ✅ robots.txt in /public
// ✅ sitemap.xml in /public or generated
// ✅ Essential for SEOPractical Examples
Example 1: Logo Component
import Image from 'next/image';
import Link from 'next/link';
export function Logo() {
return (
<Link href="/" className="flex items-center gap-2">
{/* SVG logo from public */}
<Image
src="/images/logo.svg"
alt="Company Logo"
width={40}
height={40}
priority
/>
<span className="text-xl font-bold">Company</span>
</Link>
);
}
// ✅ SVG in public folder
// ✅ priority for above-fold
// ✅ Link for navigationExample 2: Product Gallery
import Image from 'next/image';
interface Product {
id: string;
name: string;
images: string[];
}
export function ProductGallery({ product }: { product: Product }) {
const [selectedImage, setSelectedImage] = useState(0);
return (
<div className="space-y-4">
{/* Main image */}
<div className="relative aspect-square">
<Image
src={`/images/products/${product.images[selectedImage]}`}
alt={`${product.name} - View ${selectedImage + 1}`}
fill
className="object-cover rounded-lg"
priority={selectedImage === 0}
/>
</div>
{/* Thumbnails */}
<div className="grid grid-cols-4 gap-2">
{product.images.map((image, index) => (
<button
key={index}
onClick={() => setSelectedImage(index)}
className={`relative aspect-square ${
index === selectedImage ? 'ring-2 ring-blue-500' : ''
}`}
>
<Image
src={`/images/products/${image}`}
alt={`${product.name} thumbnail ${index + 1}`}
fill
className="object-cover rounded"
/>
</button>
))}
</div>
</div>
);
}
// ✅ Dynamic image paths
// ✅ Multiple views
// ✅ Priority on main imageExample 3: Downloadable Resources
interface Resource {
title: string;
description: string;
file: string;
size: string;
icon: string;
}
export function ResourcesList({ resources }: { resources: Resource[] }) {
return (
<div className="grid md:grid-cols-2 gap-6">
{resources.map((resource) => (
<div
key={resource.file}
className="bg-white p-6 rounded-lg shadow hover:shadow-lg transition"
>
<div className="flex items-start gap-4">
{/* File icon */}
<div className="w-12 h-12 flex-shrink-0">
<Image
src={`/icons/${resource.icon}`}
alt=""
width={48}
height={48}
/>
</div>
<div className="flex-1">
<h3 className="font-bold text-lg mb-1">{resource.title}</h3>
<p className="text-gray-600 text-sm mb-3">
{resource.description}
</p>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500">{resource.size}</span>
href={`/documents/${resource.file}`}
download
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm"
>
Download
</a>
</div>
</div>
</div>
</div>
))}
</div>
);
}
// Usage:
const resources = [
{
title: "User Manual",
description: "Complete guide to using our platform",
file: "user-manual.pdf",
size: "2.4 MB",
icon: "pdf-icon.svg",
},
// ... more resources
];
// ✅ Icons from public
// ✅ Documents from public
// ✅ Download linksExample 4: Background Video
export function HeroWithVideo() {
return (
<section className="relative h-screen overflow-hidden">
{/* Background video */}
<video
autoPlay
muted
loop
playsInline
className="absolute inset-0 w-full h-full object-cover"
>
<source src="/videos/hero-background.mp4" type="video/mp4" />
<source src="/videos/hero-background.webm" type="video/webm" />
</video>
{/* Overlay */}
<div className="absolute inset-0 bg-black/50" />
{/* Content */}
<div className="relative z-10 h-full flex items-center justify-center text-white text-center">
<div>
<h1 className="text-6xl font-bold mb-4">
Welcome to Our Platform
</h1>
<p className="text-xl mb-8">
Experience the future of technology
</p>
<button className="px-8 py-4 bg-blue-600 rounded-lg font-bold text-lg hover:bg-blue-700">
Get Started
</button>
</div>
</div>
</section>
);
}
// ✅ autoPlay, muted, loop for background
// ✅ playsInline for mobile
// ✅ Multiple formats for compatibility
// ✅ Overlay for readabilityComplete Asset Organization
Recommended structure for all static assets
Select a file or folder to see details
Static Asset Best Practices
1. Organize by Type
// ✅ GOOD: Organized by type
public/
├── images/
├── icons/
├── fonts/
├── documents/
└── videos/
// ❌ BAD: Everything in root
public/
├── logo.png
├── hero.jpg
├── terms.pdf
├── video.mp4
└── icon.svg
// Organized structure is easier to maintain2. Use Descriptive Names
// ✅ GOOD: Descriptive names
/images/hero-homepage-2024.jpg
/documents/privacy-policy-v2.pdf
/icons/search-icon.svg
// ❌ BAD: Generic names
/images/img1.jpg
/documents/doc.pdf
/icons/icon2.svg
// Descriptive names are self-documenting3. Optimize Before Upload
# Optimize images before adding to public
# Use tools like:
# - TinyPNG (https://tinypng.com)
# - Squoosh (https://squoosh.app)
# - ImageOptim (Mac)
# Example: Compress images
# Original: 4.2 MB
# Optimized: 400 KB (90% smaller)
# ✅ Optimize before deployment
# ✅ Smaller files = faster loading
# ✅ Better user experience4. Version Critical Files
// ✅ GOOD: Version in filename
/documents/terms-v2.pdf
/documents/privacy-policy-2024.pdf
/images/logo-v3.svg
// Allows cache busting
<a href="/documents/terms-v2.pdf">Terms</a>
// ✅ Update filename when content changes
// ✅ Forces browsers to fetch new version
// ✅ Avoids cache issues5. Don't Store Sensitive Data
# ❌ NEVER in public folder:
# - API keys
# - Passwords
# - Private documents
# - User data
# - Configuration secrets
# Public folder is publicly accessible!
# https://yoursite.com/secret-file.txt
# ✅ Use environment variables instead
# ✅ Use API routes for sensitive data
# ✅ Use databases for user dataKey Takeaways
- /public folder - at project root for static assets
- Reference with / - /images/logo.png (omit /public)
- Organize by type - images, icons, fonts, documents
- Import vs public - imports for critical, public for dynamic
- Common assets - favicon, robots.txt, sitemap.xml
- Optimize first - compress before uploading
- Descriptive names - self-documenting filenames
- No sensitive data - public is publicly accessible
🎉 Images and Media Section Complete!
You've completed the Images and Media section! You've mastered:
- ✅ Next.js Image component basics
- ✅ Advanced image optimization
- ✅ Static asset management
You now have complete mastery of images and assets in Next.js! You can optimize images for performance, organize static assets efficiently, and build applications with fast, optimized media. These skills are essential for creating professional, performant Next.js applications.
📁 Asset Management
Good asset organization pays off long-term. Spend time setting up a clear folder structure at the start of your project. Future you will thank present you when you need to find or update assets!