Static Assets

Catmint serves static files from the app/public/ directory. Files placed in this directory are served at the root URL without any processing, transformation, or hashing. This guide covers the public directory convention, how files are served, cache behavior, and recommended usage patterns.

The app/public/ Directory

The app/public/ directory is the designated location for static assets. Any file placed here is served directly at the corresponding root URL path:

app/
  public/
    favicon.ico          -> /favicon.ico
    robots.txt           -> /robots.txt
    manifest.json        -> /manifest.json
    og-image.png         -> /og-image.png
    images/
      logo.svg           -> /images/logo.svg
      hero.jpg           -> /images/hero.jpg
    fonts/
      inter.woff2        -> /fonts/inter.woff2

The directory structure inside public/ is preserved in the URL path. Subdirectories become path segments.

No Processing or Transformation

Files in app/public/ are served exactly as they are. Unlike assets imported in components (which go through Vite's build pipeline), public files receive:

  • No bundling -- files are not processed by Vite
  • No hashing -- filenames are not modified with content hashes
  • No minification -- content is served as-is
  • No transformation -- images are not optimized, CSS is not compiled

This makes public/ ideal for files that must be served at a fixed, predictable URL -- such as favicon.ico or robots.txt -- and files that are already optimized.

Referencing Static Assets

Reference public files using absolute URLs from the root. Since the files are served at the root path, use a leading slash:

// app/layout.tsx
import React from 'react'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <link rel="icon" href="/favicon.ico" />
        <link rel="manifest" href="/manifest.json" />
        <meta property="og:image" content="/og-image.png" />
      </head>
      <body>
        <header>
          <img src="/images/logo.svg" alt="Logo" width={120} height={40} />
        </header>
        <main>{children}</main>
      </body>
    </html>
  )
}

Cache Headers in Production

In production, Catmint serves static assets with appropriate cache headers. Since public files do not have content hashes in their filenames, they use conservative cache settings by default:

EnvironmentCache-Control
Developmentno-cache
Productionpublic, max-age=3600, immutable (for hashed assets via Vite); public, max-age=300 (for public/ files)

Because public files can change between deployments without a filename change, they receive a shorter max-age than Vite-processed assets (which have content hashes and can be cached indefinitely). You can adjust cache behavior by configuring your deployment adapter or CDN.

Recommended Files for app/public/

Place the following types of files in the public directory:

FilePurpose
favicon.icoBrowser tab icon
robots.txtSearch engine crawling directives
manifest.jsonPWA web app manifest
sitemap.xmlSearch engine sitemap
og-image.pngOpen Graph social sharing image
.well-known/*Verification files, security.txt, etc.

Public Files vs. Imported Assets

Catmint supports two ways to include assets. Choose the right approach based on your needs:

Featureapp/public/ FilesImported Assets
URLFixed, predictable pathHashed filename
CachingConservative (shorter TTL)Aggressive (immutable, long TTL)
ProcessingNoneOptimized by Vite
Best forfavicon, robots.txt, manifestImages in components, CSS

Importing Assets in Components

For assets that are referenced in components and benefit from Vite's processing pipeline, import them directly instead of placing them in public/:

// app/components/Hero.tsx
import heroImage from './hero.jpg'

export function Hero() {
  // heroImage is a hashed URL like /assets/hero-a1b2c3d4.jpg
  return (
    <section>
      <img src={heroImage} alt="Hero banner" />
    </section>
  )
}

Imported assets go through Vite's build pipeline, receiving content hashing, optimization, and aggressive cache headers. Use this approach for any asset that is referenced in your component code.

Example: robots.txt and manifest.json

# app/public/robots.txt
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
// app/public/manifest.json
{
  "name": "My Catmint App",
  "short_name": "Catmint App",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Next Steps