How Static Assets Are Organized and Served Efficiently from the Public Folder in a Vite-Based Frontend

In the tcg-pocket-collection-tracker repository, card images and icons are stored in frontend/public/ and referenced via absolute paths starting with /, leveraging Vite's built-in static file serving to eliminate bundling overhead and enable aggressive caching.

The tcg-pocket-collection-tracker project manages hundreds of trading card game assets using a React frontend powered by Vite. Understanding how static assets are organized and served efficiently from the public folder is critical for maintaining fast load times and seamless internationalization across the application.

Understanding Vite's Public Directory Convention

Vite treats the frontend/public directory as a static-asset root. During the build process, files in this folder are copied unchanged into frontend/dist and served directly from the web server at the same relative URL they have inside public. This bypasses Vite's module graph entirely, eliminating bundling overhead for images and other binary assets.

Directory Structure for Card Images and Icons

The project organizes visual assets hierarchically within frontend/public/images/ to support localization and easy maintenance.

Set Artwork Organization

Card set artwork is stored in locale-specific subdirectories to support internationalization. Each expansion has a dedicated WebP file named after its ID.


frontend/public/images/sets/
├── en-US/
│   └── P-B.webp
├── de-DE/
│   └── ...
└── ...

For example, the English version of the "P-B" expansion logo resides at frontend/public/images/sets/en-US/P-B.webp.

Energy Type Icons

Energy type icons (Water, Fire, etc.) are stored in a separate directory for reuse across components like deck builders and card tables.


frontend/public/images/energy/
├── water.webp
├── fire.webp
└── ...

Referencing Static Assets in React Components

Components reference these assets using absolute paths starting with /, which maps directly to the public folder root.

Locale-Aware Image Loading with Fallbacks

The application implements a fallback pattern to handle missing localized images. If a locale-specific file doesn't exist, the component automatically switches to the English version using the onError event handler.

In frontend/src/pages/overview/components/ExpansionOverview.tsx (line 40):

<img
  src={`/images/sets/${i18n.language}/${expansion.id}.webp`}
  onError={e => {
    // fallback to English if the localized file is not present
    (e.target as HTMLImageElement).src = `/images/sets/en-US/${expansion.id}.webp`;
  }}
  alt={expansion.name}
/>

Similarly, frontend/src/components/CardsTable.tsx (line 97) uses the same pattern for displaying set icons in table rows:

<img
  src={`/images/sets/${i18n.language}/${row.expansion.id}.webp`}
  onError={e => {
    // fallback to English when localized image is missing
    (e.target as HTMLImageElement).src = `/images/sets/en-US/${row.expansion.id}.webp`;
  }}
  alt={row.expansion.name}
  className="mr-2 h-6 w-6"
/>

Direct Icon References

Energy icons are referenced directly without fallback logic, as seen in components like frontend/src/pages/decks/DeckItem.tsx:

<img src={`/images/energy/${energyType}.webp`} alt={energyType} className="h-4 w-4" />

Performance Benefits of This Approach

Organizing static assets in the public folder provides several performance advantages:

  1. Zero Bundling Overhead – Files in public bypass Vite's module graph entirely. They are not hashed, tree-shaken, or processed, reducing build time and memory usage.

  2. Aggressive Caching – Static URLs like /images/sets/en-US/P-B.webp remain consistent across deployments. Servers and CDNs can apply long-term cache headers (e.g., Cache-Control: max-age=31536000) without worrying about cache invalidation from hashed filenames.

  3. Optimized Fallback Strategy – The onError pattern ensures only one HTTP request is made per image. If the localized version returns 404, the browser immediately falls back to the English version without additional round trips or complex state management.

  4. Fast Build Process – Vite copies the entire public directory to dist as a simple file operation during pnpm build, significantly faster than importing and processing images through the JavaScript module system.

Summary

  • The tcg-pocket-collection-tracker uses Vite's public directory as a static asset root for card images and icons.
  • Assets are organized under frontend/public/images/ with subdirectories for sets (locale-specific) and energy (type icons).
  • Components reference assets via absolute paths (/images/...) and implement onError fallbacks to English versions when localized images are missing.
  • This approach eliminates bundling overhead, enables aggressive caching, and maintains fast build times.

Frequently Asked Questions

Why store images in the public folder instead of importing them in components?

Importing images through JavaScript modules causes Vite to process them through its build pipeline, adding hash fingerprints to filenames and potentially inflating bundle size. Placing assets in public keeps them as static files with predictable URLs, enabling better caching and reducing build complexity for binary assets that don't benefit from module processing.

How does the application handle missing localized card images?

The React components use the native onError event handler on img tags. If a request for a localized image (e.g., /images/sets/de-DE/P-B.webp) returns a 404, the handler immediately updates the src attribute to the English fallback (/images/sets/en-US/P-B.webp). This ensures users always see a valid image without requiring complex pre-check logic or additional HTTP requests.

What file format is used for the card images and why?

The project uses WebP (.webp) for all card images and icons. WebP provides superior compression compared to PNG or JPEG, resulting in smaller file sizes without significant quality loss. This is crucial for a card collection tracker where users may browse hundreds of images, as it reduces bandwidth usage and improves page load times, especially on mobile devices.

Can the static asset paths be cached aggressively by browsers?

Yes. Because assets in the public folder are served with consistent URLs (e.g., /images/sets/en-US/P-B.webp) rather than hashed filenames, they are ideal for long-term caching. The server or CDN can set Cache-Control headers with long max-age values (such as one year), and when images need updating, developers simply replace the file content while keeping the same filename, allowing cache invalidation strategies to work predictably.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →