Blog Implementation Guides 7 min read

Express and EJS Image Fallbacks for Server-Rendered Apps

Handle missing and broken images in Express.js EJS templates using route handlers, res.locals helpers, and fallback.pics placeholder URLs for empty media fields.

express ejs image placeholderexpress image fallbackejs templatenodejs image fallbackexpress.js
Express and EJS Image Fallbacks for Server-Rendered Apps

Express apps using EJS for server-rendered HTML can resolve image fallbacks at two layers: in route handlers before passing data to templates, and in EJS template helpers registered on res.locals that handle null or empty image fields.

Server-side resolution ensures that social crawlers, email clients, and first-paint users see correct images. Client-side onerror is a secondary safety net for CDN failures after the page loads.

Route handler

Resolve fallback URLs in Express route handlers

The route handler that queries the database is the right place to resolve null image fields. Add a fallback URL to the data object before passing it to res.render().

This keeps EJS templates free of conditional logic and ensures every downstream consumer of the data — email notifications, JSON API responses, scheduled jobs — gets the same resolved URL.

Implementation text
// routes/products.js
router.get('/products/:slug', async (req, res) => {
  const product = await Product.findOne({ slug: req.params.slug });
  if (!product) return res.status(404).render('404');

  const imageUrl = product.imageUrl
    ?? `https://fallback.pics/api/v1/800x800?text=${encodeURIComponent(product.name)}`;

  res.render('products/show', { product, imageUrl });
});

EJS template

Render fallback URLs in EJS templates

With the fallback URL resolved in the route handler, EJS templates simply reference the variable. Add an onerror attribute for files that pass the null check but return a CDN error.

EJS escapes HTML in <%= %> tags by default. That behavior protects against XSS in URL values without additional effort.

Implementation text
<%-- views/products/show.ejs --%>
<img
  src="<%= imageUrl %>"
  alt="<%= product.name %>"
  width="800"
  height="800"
  onerror="this.onerror=null; this.src='https://fallback.pics/api/v1/800x800?text=Not+Found'"
  loading="lazy"
/>

Middleware helper

Register an imageUrl helper on res.locals

For fallback logic used across many routes, register a helper function on res.locals in middleware. EJS templates can call it directly, avoiding repetition in individual route handlers.

Implementation text
// middleware/templateHelpers.js
app.use((req, res, next) => {
  res.locals.imageUrl = function(src, text, w = 400, h = 400) {
    if (src) return src;
    return `https://fallback.pics/api/v1/${w}x${h}?text=${encodeURIComponent(text)}`;
  };
  next();
});

// In EJS template
<img
  src="<%= imageUrl(product.imageUrl, product.name, 800, 800) %>"
  alt="<%= product.name %>"
  width="800"
  height="800"
/>

User avatars

Avatar fallbacks with initials in Express

Register an avatarUrl helper in middleware that generates an initials-based avatar URL when the user has no profile photo.

This keeps avatar fallback logic in one place even when avatars appear in multiple templates — navigation headers, comment threads, and profile pages.

Implementation text
// In middleware
res.locals.avatarUrl = function(user) {
  if (user.avatar) return user.avatar;
  const initials = user.name
    .split(' ')
    .map(n => n[0])
    .join('')
    .toUpperCase()
    .slice(0, 2);
  return `https://fallback.pics/api/v1/avatar/64?text=${encodeURIComponent(initials)}`;
};

// In EJS template
<img
  src="<%= avatarUrl(user) %>"
  alt="<%= user.name %>"
  width="64"
  height="64"
  onerror="this.onerror=null; this.src='https://fallback.pics/api/v1/avatar/64?text=??'"
/>

OG image

Pass og:image to EJS layouts

For blog and product pages that need social sharing previews, pass an ogImage variable to the template and render it in the <head> partial.

Compute the fallback URL in the route handler with OG-appropriate dimensions. Append .jpg for social platform compatibility.

Implementation tsx
// Route handler
const ogImage = post.featuredImage
  ?? `https://fallback.pics/api/v1/1200x630.jpg?text=${encodeURIComponent(post.title)}`;
res.render('blog/post', { post, ogImage });

<%-- views/partials/head.ejs --%>
<meta property="og:image" content="<%= ogImage %>" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

Security

Escape user-controlled content in EJS templates

EJS escapes HTML in <%= %> tags by default. Use <%- %> only for pre-rendered HTML you control entirely. For URLs containing user-supplied text, use encodeURIComponent before embedding in the fallback URL.

Never put raw user input into an onerror JavaScript string. The fallback URL in the onerror attribute should be a hard-coded string without user-controlled segments.

Key takeaways

What to standardize before shipping

  • Resolve null image fields in Express route handlers before passing data to res.render() so templates stay clean.
  • Register an imageUrl helper on res.locals in middleware for fallback URL logic used across many routes.
  • EJS escapes HTML in <%= %> by default; use encodeURIComponent for user-controlled text in fallback URL parameters.
  • Set og:image in the route handler and pass it to the EJS layout for social sharing previews.
  • Keep the onerror attribute value as a hard-coded fallback URL — never construct it from user-controlled input.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs