ForgeWP

The WpImage Primitive & Responsive Media

How to use <WpImage> for responsive featured images, static assets with src, attachment resolution, and native wp_get_attachment_image compilation.

Images in a WordPress theme serve two fundamentally different roles:

  1. Content Images (Dynamic CMS Media): Blog post featured images, portfolio galleries, author avatars, and product photography uploaded by content editors via the WordPress Media Library.
  2. Design Assets (Static Theme Media): Brand logos, UI illustrations, background textures, and placeholder graphics bundled directly inside your theme codebase.

In traditional WordPress PHP themes, rendering an image responsibly requires nesting complex functions (wp_get_attachment_image, wp_get_attachment_image_src, wp_calculate_image_srcset, wp_calculate_image_sizes). In pure React applications, developers often use basic <img /> tags that fail to integrate with WordPress's media database.

ForgeWP solves this with <WpImage> from @forgewp/react. It provides an isomorphic, type-safe React component in development that compiles to high-performance WordPress wp_get_attachment_image() calls or get_theme_file_uri() in production.


1. How <WpImage> Works Across Environments

The following diagram illustrates how <WpImage> bridges local Vite development with native WordPress server rendering:

Flowchart
Rendering diagram...
  1. In Local Development (pnpm dev): <WpImage> reads image mock data from cms/mock-data.ts through the React WpPostContext. If an image is missing or you use a random seed, it automatically displays a responsive placeholder so you can style your layout immediately.
  2. At Compile & Export (pnpm export): The compiler analyzes <WpImage> attributes and transforms the tag into native WordPress PHP (wp_get_attachment_image or get_theme_file_uri).
  3. On the Live WordPress Server: For a Media Library attachment, the generated PHP calls WordPress image functions, which use the site's registered image sizes and responsive attributes. Static theme assets follow a separate path.

2. Quick Decision Guide: Which Prop to Use?

Before diving into details, use this quick reference matrix to choose the right <WpImage> prop for your media:

Table · 3 columns
I want to display...
Prop to Use
Code Example
Current post's featured image
field="featuredImage"
<WpImage field="featuredImage" size="large" />
ACF or custom field image
field="meta_key"
<WpImage field="author_photo" size="medium" />
Specific WordPress Media ID
id={attachmentId}
<WpImage id={142} size="full" />
Static image from src/assets/
src={importedGraphic}
<WpImage src={heroGraphic} alt="Hero" />
Public static file from public/
src="/assets/..."
<WpImage src="/assets/logo.svg" width={180} height={40} />
Remote CDN or Unsplash URL
src="https://..."
<WpImage src="https://images.unsplash.com/..." />

3. Rendering Post Featured Images (field="featuredImage")

The most common use case for <WpImage> is rendering the featured image (post thumbnail) of an article or custom post type.

Basic Implementation

src/components/PostCard.tsx
1import { WpImage, useWpTitle, useWpExcerpt } from "@forgewp/react"
2
3export function PostCard() {
4  const title = useWpTitle()
5  const excerpt = useWpExcerpt()
6
7  return (
8    <article className="overflow-hidden rounded-2xl border border-border bg-card shadow-sm transition hover:shadow-md">
9      {/* Featured image with large thumbnail size */}
10      <WpImage
11        field="featuredImage"
12        size="large"
13        className="aspect-video w-full object-cover"
14        alt={title}
15      />
16
17      <div className="p-6">
18        <h3 className="mb-2 text-xl font-bold text-foreground">{title}</h3>
19        <p className="text-sm leading-relaxed text-muted-foreground">
20          {excerpt}
21        </p>
22      </div>
23    </article>
24  )
25}

What the Compiler Outputs in PHP

When you export your theme, the JSX above compiles directly to:

PHP Output in single.php / index.php
1<?php echo wp_get_attachment_image(
2    get_post_thumbnail_id( get_the_ID() ),
3    'large',
4    false,
5    array(
6        'class' => 'w-full h-56 object-cover',
7        'alt'   => esc_attr( get_the_title() ),
8    )
9); ?>

Notice that:

  • It calls WordPress's native wp_get_attachment_image().
  • WordPress automatically populates the srcset attribute with all image dimensions generated upon upload (thumbnail, medium, medium_large, large).
  • It preserves your Tailwind CSS classes in the output HTML.

3. Rendering Static & Local Assets with src

Beyond dynamic CMS uploads, you will frequently need to render static theme graphics: brand logos, decorative backgrounds, hero vectors, or bundled illustrations.

<WpImage> supports the src prop in three flexible ways:

Pattern A: Static Asset Import (Vite-Hashed)

When you import an image file directly from src/assets/, Vite treats it as a static asset module:

src/components/HeroSection.tsx
1import { WpImage } from "@forgewp/react"
2import heroPattern from "@/assets/hero-pattern.png"
3
4export function HeroSection() {
5  return (
6    <section className="relative overflow-hidden bg-background py-24">
7      {/* Vite automatically provides intrinsic width, height, and hashed src */}
8      <WpImage
9        src={heroPattern}
10        alt="Decorative background pattern"
11        className="absolute inset-0 h-full w-full object-cover opacity-15"
12      />
13      <div className="relative z-10 mx-auto max-w-4xl text-center">
14        <h1 className="text-5xl font-black text-foreground">
15          Next-Gen Architecture
16        </h1>
17      </div>
18    </section>
19  )
20}

Pattern B: Absolute Path from public/

For static files stored in your project's public/ directory (e.g. public/assets/logo.svg):

src/components/SiteHeader.tsx
1<WpImage
2  src="/assets/logo.svg"
3  width={180}
4  height={40}
5  alt="ForgeWP Company Logo"
6  className="h-10 w-auto"
7/>

Pattern C: External CDN or Remote URLs

You can pass any fully qualified remote URL (https://...):

src/components/ExternalBanner.tsx
1<WpImage
2  src="https://images.unsplash.com/photo-example"
3  width={800}
4  height={600}
5  alt="Sample Unsplash Photo"
6  className="rounded-xl shadow"
7/>

4. How the Compiler Identifies Images (Demystifying the "Magic")

Developers often ask: How does ForgeWP know whether to compile <WpImage> into a WordPress PHP function or a static HTML tag?

There is no black magic. The compilation pipeline operates in two clean, deterministic stages:

Stage 1: Server-Side Pre-Rendering (Intermediate Custom Element)

During pnpm export, ForgeWP evaluates your React components in a Node.js SSR context with _forgeWpCompileTime = true.

Instead of emitting a normal <img> tag, <WpImage> renders a temporary custom XML marker:

Intermediate Output from React SSR
1<forgewp-image
2  data-field="featuredImage"
3  data-size="large"
4  data-class-name="w-full h-56 object-cover"
5  data-alt="Article Title"
6></forgewp-image>

If you used src="/assets/logo.svg", it produces:

Intermediate Output for Static Asset
1<forgewp-image
2  data-src="/assets/logo.svg"
3  data-width="180"
4  data-height="40"
5  data-alt="ForgeWP Company Logo"
6></forgewp-image>

Stage 2: The Markup Processor's 5-Branch Evaluation

Next, ForgeWP's compiler engine (markup-processor.js) parses all <forgewp-image> tags and executes a deterministic branching rule:

Flowchart
Rendering diagram...
Table · 3 columns
Tag Attributes
Compiler Logic
Emitted Output in PHP Template
id={142} or src={142}
Numeric Attachment ID
<?php echo wp_get_attachment_image( 142, 'large', false, ... ); ?>
field="featuredImage"
Current Post Thumbnail
<?php echo wp_get_attachment_image( get_post_thumbnail_id( get_the_ID() ), 'large', false, ... ); ?>
field="author_photo"
Custom Meta Key
PHP conditional checking if meta is an attachment ID or image URL string
src="/assets/logo.svg"
Local Theme File
<img src="<?php echo esc_url( get_theme_file_uri( 'assets/logo.svg' ) ); ?>" ... />
src="https://..."
External Web URL
<img src="https://..." class="..." alt="..." ... />

5. WordPress Image Sub-Sizes (size)

WordPress automatically generates multiple cropped versions of every uploaded image. You can specify which registered image size <WpImage> should request using the size prop:

Table · 3 columns
Prop Value
Default WordPress Dimensions
Typical Theme Use Case
size="thumbnail"
150 × 150 px (Square crop)
Compact sidebar lists, user avatars, comment threads
size="medium"
Max 300 × 300 px
Mobile card previews, grid thumbnails
size="large"
Max 1024 × 1024 px
Post featured headers, standard card grids
size="full"
Original uploaded dimensions
Full-width hero banners, lightbox zoom views
size="custom-size"
Custom registered size
Any size declared via add_image_size()
src/components/HeroBanner.tsx
1<WpImage
2  field="featuredImage"
3  size="full"
4  priority={true}
5  className="max-h-150 w-full rounded-3xl object-cover"
6/>

6. Custom Field & ACF Image Attachments

Themes frequently display secondary images attached to posts via custom fields or Advanced Custom Fields (ACF)—such as author portraits, product galleries, or sponsor logos.

Pass the meta key name to the field prop:

src/components/AuthorBio.tsx
1import { WpImage, useWpAuthor, useWpCustomField } from "@forgewp/react"
2
3export function AuthorBio() {
4  const author = useWpAuthor()
5  const bio = useWpCustomField("author_bio", "")
6
7  return (
8    <aside className="flex items-center gap-4 rounded-xl border border-border bg-card p-6">
9      {/* Custom field containing attachment ID or image URL */}
10      <WpImage
11        field="author_profile_photo"
12        size="thumbnail"
13        className="h-16 w-16 rounded-full border-2 border-primary object-cover"
14        alt={author}
15      />
16
17      <div>
18        <h4 className="font-bold text-foreground">{author}</h4>
19        <p className="text-xs text-muted-foreground">{bio}</p>
20      </div>
21    </aside>
22  )
23}

Smart Isomorphic Resolution for Custom Fields

In WordPress, custom fields can store images in two ways:

  1. As an Attachment ID (integer, e.g. 142): The compiler outputs a PHP check that calls wp_get_attachment_image(142, 'thumbnail', ...) with full responsive srcset.
  2. As a Direct URL string (https://...): If the field stores a raw URL string, the compiler falls back to <img src="..." /> with escaped attributes.

7. Performance Optimization: priority & Core Web Vitals

Largest Contentful Paint (LCP) is one of Google's most critical Core Web Vitals metrics. Browsers penalize sites that lazy-load above-the-fold hero images.

ForgeWP provides the priority prop:

src/app/page.tsx
1<WpImage
2  field="featuredImage"
3  size="full"
4  priority={true}
5  alt="Featured Hero Story"
6  className="h-96 w-full object-cover"
7/>

When priority={true} is set:

  1. fetchpriority="high" is added to prioritize network downloading in Chromium browsers.
  2. loading="eager" is applied (disabling native lazy loading).
  3. The compiler may also add a preload for a statically resolved image source. For dynamic WordPress attachment fields, inspect the generated output to check preload behavior.

For standard below-the-fold content images, omit priority (or specify loading="lazy"). The component defaults to lazy loading and asynchronous decoding.


8. Complete Props Reference for <WpImage>

Table · 4 columns
Prop
Type
Default
Description
field
string
undefined
Post meta key (e.g. "thumbnail", "featuredImage", "banner_img").
id
number
undefined
Specific WordPress Media Library attachment ID.
src
string | StaticImageData | number
undefined
Local asset path, external URL, or static import object.
size
string
"full"
Registered WordPress image size ("thumbnail", "medium", "large", "full").
alt
string
""
Image description for accessibility and SEO.
width
number | string
undefined
Intrinsic image width in pixels.
height
number | string
undefined
Intrinsic image height in pixels.
priority
boolean
false
When true, enables eager loading, fetchpriority="high", and head preloading.
loading
"lazy" | "eager"
"lazy" (or "eager" if priority)
Browser loading strategy.
decoding
"async" | "sync" | "auto"
"async"
Image decoding strategy.
sizes
string
undefined
CSS media condition sizes string (e.g. "(max-width: 768px) 100vw, 50vw").
placeholder
"empty" | "blur"
"empty"
Blur-up placeholder mode while image loads.
blurDataURL
string
undefined
Base64-encoded micro-blur image string.

Common Questions & FAQs

Can I use a standard HTML `<img />` tag instead of `<WpImage />`?

Yes, you can use regular <img src="..." /> tags for hardcoded static SVGs or third-party CDN images. However, for any content connected to WordPress (featured images, user uploads, custom fields), always use <WpImage>. Standard <img> tags cannot look up WordPress attachment IDs or generate responsive srcset variations.

How do I provide mock featured images during local development?

In cms/mock-data.ts, define featuredImage: "/assets/hero.jpg" on a mock post. Use <WpImage field="featuredImage" /> to read that property in local preview and compile it to WordPress's featured-image output.


Next Step: Local Assets, SVGs & Dynamic Icons

Proceed to Local Assets, SVGs & Dynamic Icons to learn how to bundle theme assets from public/ and src/assets/, render vector graphics, and use the tree-shaken <WpIcon> primitive!