ForgeWP

Local Assets, SVGs & Dynamic Icons

Bundling local theme assets in public/ vs src/assets/, resolving dynamic runtime paths with useWpThemeUri, and using the tree-shaken <WpIcon> primitive.

While post featured images and user uploads reside in the WordPress Media Library, theme-specific branding—such as site logos, decorative vector artwork, UI sound effects, and icon sets—lives inside your theme repository.

ForgeWP provides a clean dual-folder strategy for static files, automatic WordPress theme URI resolution, the useWpThemeUri() hook for dynamic runtime paths, and an integrated zero-runtime <WpIcon> component.


1. Where to Store Local Theme Assets

In a ForgeWP project, static assets live in one of two directories depending on whether you want Vite to bundle and hash them:

Theme Asset Structure
my-theme/
├── public/                     # Static Root Assets (copied as-is)
│   ├── favicon.ico
│   ├── robots.txt
│   └── screenshot.png          # WordPress theme preview card (1200x900)
│
├── src/
│   ├── assets/                 # Bundled Assets (processed by Vite)
│   │   ├── logo.svg
│   │   ├── hero-pattern.png
│   │   └── vectors/
│   │       └── illustration.svg

Option A: public/ (Unprocessed Static Files)

Any file placed inside public/ is copied directly into the root of your compiled WordPress theme ZIP without changing its filename.

  • Best for: favicon.ico, robots.txt, WordPress screenshot.png, or files that must maintain fixed, predictable URLs.
  • Referencing in Code: Reference directly with a leading slash:
src/components/Header.tsx
1import { WpImage } from "@forgewp/react"
2
3  export function SiteLogo() {
4    return (
5      <WpImage
6        src="/assets/logo.svg"
7        alt="Brand Logo"
8        width={180}
9        height={40}
10        className="h-8 w-auto"
11      />
12    )
13  }

Option B: src/assets/ (Vite-Processed Assets)

Files inside src/assets/ can be imported directly into your TypeScript components. Vite processes them as build assets and generates cache-busting URLs; the separate ForgeWP Sharp pipeline may generate WebP variants for eligible raster files.

  • Best for: Component-specific illustrations, background images, and UI graphics.
  • Referencing in Code:
src/components/Hero.tsx
1import heroGraphic from "@/assets/hero-pattern.png"
2  import { WpImage } from "@forgewp/react"
3
4  export function Hero() {
5    return (
6      <section className="relative overflow-hidden py-20">
7        <WpImage
8          src={heroGraphic}
9          alt="Abstract decorative pattern"
10          className="absolute inset-0 -z-10 h-full w-full object-cover opacity-20"
11        />
12      </section>
13    )
14  }

2. Automatic URL Rewriting (get_theme_file_uri)

In traditional WordPress PHP development, linking to any theme file requires wrapping every path in get_theme_file_uri():

Traditional PHP Boilerplate
1<img src="<?php echo esc_url( get_theme_file_uri( 'assets/logo.svg' ) ); ?>" />

If you hardcode relative paths like <img src="./assets/logo.svg" />, the browser breaks on child routes (/blog/post-name/) because relative paths resolve against the current URL instead of the theme root.

ForgeWP eliminates this boilerplate across both <WpImage> and standard HTML tags:

src/components/SiteHeader.tsx
1// Approach 1 (Recommended): Using <WpImage>
2<WpImage src="/assets/logo.svg" alt="ForgeWP" width={180} height={40} className="h-9 w-auto" />
3
4// Approach 2: Using standard <img> tag (Supported as a zero-overhead fallback for SVGs)
5<img src="/assets/logo.svg" alt="ForgeWP" className="h-9 w-auto" />

At compile time (pnpm export), the compiler analyzes static asset paths across both <WpImage> and <img> tags and emits native WordPress PHP:

Compiled Output in header.php
1<img src="<?php echo esc_url( get_theme_file_uri( 'assets/logo.svg' ) ); ?>" alt="ForgeWP" class="h-9 w-auto" />

In local Vite development (pnpm dev), Vite serves the file instantly from disk with zero 404 errors.

Static download links under public/assets/ are also rewritten to the active theme URI:

TSX
1<a href="/assets/client-assets/preview.pdf">View preview</a>

For an asset URL that comes from block attributes or other runtime data, use resolveWpAsset() so it resolves against the theme directory in WordPress while remaining a public-path URL in Vite:

TSX
1import { resolveWpAsset } from "@forgewp/react"
2
3<a href={resolveWpAsset(previewUrl)}>View preview</a>

In generated block PHP, the compiler resolves this helper server-side as well, so the URL works before any client-side hydration.


3. Dynamic Runtime Asset Paths with useWpThemeUri()

The compiler automatically rewrites static paths inside JSX elements like <WpImage src="/assets/..." />. But what if you are building an interactive client component that needs to load an asset dynamically in JavaScript?

Examples include:

  • Playing an audio notification with new Audio()
  • Loading a 3D model or canvas texture in Three.js
  • Dynamically requesting a theme JSON file via fetch()

In the browser, client JavaScript cannot execute PHP's get_theme_file_uri(). ForgeWP provides the useWpThemeUri() hook from @forgewp/react:

src/components/AudioNotification.tsx
1import { useWpThemeUri } from "@forgewp/react"
2
3export function AudioNotification() {
4  // Returns the active WordPress theme root URI
5  const themeUri = useWpThemeUri()
6
7  const handlePlaySound = () => {
8    // Dynamically construct asset path at runtime
9    const sound = new Audio(`${themeUri}/assets/sounds/success.mp3`)
10    sound.play()
11  }
12
13  return (
14    <button
15      onClick={handlePlaySound}
16      className="rounded-lg bg-primary px-4 py-2 font-semibold text-primary-foreground shadow"
17    >
18      Play Sound
19    </button>
20  )
21}

How useWpThemeUri() Works

  • In Local Development (pnpm dev): Returns "" (empty string) so ${themeUri}/assets/... resolves cleanly to /assets/... from your local dev server.
  • In Production WordPress: ForgeWP's theme engine automatically exposes window.forgeWpHydration.themeUri on pages with interactive islands. The hook reads this value, ensuring your dynamic scripts point to the live /wp-content/themes/my-theme URL without hardcoding domain names.

When to Use What: Summary

Table · 3 columns
Use Case
Recommended Tool
Why?
Visual images & graphics in JSX
<WpImage src="..." />
Compiler rewrites URL + adds intrinsic dimensions and preloading.
Simple vector logos/SVGs in JSX
<WpImage> or <img />
Both are automatically rewritten to get_theme_file_uri() at build time.
Dynamic JavaScript logic (Audio, Canvas, Fetch)
useWpThemeUri()
Provides the live theme root URL to browser scripts at runtime.

4. Dynamic Icons with <WpIcon>

Icons are an essential part of modern web design. However, bundling an entire icon library (like Lucide or FontAwesome) in client JavaScript adds hundreds of kilobytes of unnecessary bloat.

ForgeWP includes <WpIcon>, an isomorphic primitive that dynamically renders vector glyphs:

src/components/FeatureGrid.tsx
1import { WpIcon } from "@forgewp/react"
2
3interface Feature {
4  title: string
5  description: string
6  iconName: string
7}
8
9export function FeatureCard({ title, description, iconName }: Feature) {
10  return (
11    <div className="rounded-xl border border-border bg-card p-6 shadow-sm">
12      <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
13        {/* Dynamic icon slug */}
14        <WpIcon name={iconName} provider="lucide" className="h-6 w-6" />
15      </div>
16      <h4 className="mb-1 text-lg font-bold text-foreground">{title}</h4>
17      <p className="text-sm text-muted-foreground">{description}</p>
18    </div>
19  )
20}

Supported Icon Providers

You can specify the icon set via the provider prop:

Table · 3 columns
Provider
Description
Examples
provider="lucide" (Default)
Uses Lucide React icons.
name="star", name="map-pin", name="arrow-right"
provider="dashicons"
WordPress core administrative Dashicons.
name="admin-site", name="dashboard", name="menu"
provider="custom"
Custom theme SVG sprite map.
Custom SVG slugs configured in your theme

How <WpIcon> Compiles in Production

  • In Local Development: Loads the icon dynamically via ESM module resolution with a fallback placeholder if the icon name is mis-typed.
  • In Production Theme Export: The compiler parses the icon name, extracts only the required SVG path data from the icon library, and bakes the raw vector SVG directly into the static PHP template.
  • Client Overhead: 0 KB client JavaScript! Visitors download only pure inline SVG paths.

5. Best Practices for Vector SVGs in Themes

  1. Inline SVGs for Interactive Elements: If you need CSS hover color transitions or animations, import the SVG as a React component:
src/components/SocialLinks.tsx
1export function TwitterIcon({ className }: { className?: string }) {
2     return (
3       <svg viewBox="0 0 24 24" fill="currentColor" className={className}>
4         <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
5       </svg>
6     )
7   }
  1. Use <WpImage> or <img> for Complex Illustrations: Heavy vector illustrations with hundreds of anchor points should be kept as standalone .svg files in src/assets/ to avoid bloating your JavaScript bundle.
  2. Always Include aria-hidden on Decorative Icons: If an icon is accompanied by text, add aria-hidden="true" so screen readers do not redundantly announce it.

Common Questions & FAQs

Can I use standard `<img>` tags instead of `<WpImage>` for local theme images?

Yes. Both <WpImage src="/assets/..." /> and <img src="/assets/..." /> are rewritten to use the theme file URI. The Sharp pipeline can generate WebP variants for eligible raster assets, but the current compiler does not attach those variants as srcset to every static image. WordPress attachments rendered with wp_get_attachment_image() use WordPress's own responsive sizes.

What should the WordPress theme screenshot be?

WordPress requires a screenshot.png in the theme root to display your theme preview in Appearance → Themes. Save a 1200 × 900 px PNG file in your project's public/screenshot.png. ForgeWP automatically copies it to the theme root during pnpm export.


Next Step: The Sharp Image Optimization Pipeline

Proceed to The Sharp Image Optimization Pipeline to learn how ForgeWP automates WebP conversions and preloads critical hero media during export!