DEV Community

Cover image for Designing a Resilient UI: Handling Failures Gracefully in Frontend Applications
Stanley J
Stanley J

Posted on

Designing a Resilient UI: Handling Failures Gracefully in Frontend Applications

Introduction

No matter how robust our applications are, failures are inevitable. APIs may fail, networks can be unreliable, and unexpected errors will always creep in. As frontend engineers, our job isn’t just to build beautiful UIs—it’s to design experiences that remain functional and user-friendly even when things go wrong.

In this post, we’ll explore how to build resilient UIs by anticipating failures, handling them gracefully, and ensuring a smooth user experience even in adverse conditions.

Embrace the ‘Fail Fast, Recover Gracefully’ Mindset

A resilient UI doesn’t just detect failures—it recovers from them efficiently. Instead of letting users get stuck with a broken page, we should:

✅ Detect errors early (fail fast)
✅ Provide meaningful feedback (graceful recovery)
✅ Allow users to retry or recover

Example: If a data-fetching API fails, show a retry button instead of an empty screen.

{error ? (
  <div>
    <p>Oops! Something went wrong.</p>
    <button onClick={retryFetch}>Retry</button>
  </div>
) : (
  <DataComponent data={data} />
)}
Enter fullscreen mode Exit fullscreen mode

Use Skeletons & Optimistic UI for Seamless Feedback

Ever waited for content to load and felt frustrated by a blank screen? Avoid this by implementing:

  • Skeleton loaders instead of spinners to give users an immediate visual cue.
  • Optimistic UI updates, where actions like "liking" a post update the UI instantly while confirming with the server in the background.

Example of Optimistic UI for a Like Button:

const [liked, setLiked] = useState(false);

const handleLike = async () => {
  setLiked(true); // Instantly update UI
  try {
    await api.likePost(); // Server confirmation
  } catch (error) {
    setLiked(false); // Rollback if failed
  }
};
Enter fullscreen mode Exit fullscreen mode

Provide Meaningful Error Messages (Not Just ‘Something Went Wrong’)

Users hate vague error messages. Instead of "Error: Request failed", provide actionable information:

🚫 Bad UX: "Something went wrong. Try again later."
✅ Good UX: "Unable to fetch data. Check your internet connection or try again."

Pro Tip: Use error boundaries in React to catch unexpected UI crashes.

Implement Smart Retry & Fallback Strategies

If a request fails, don’t just give up—implement smart retry logic with exponential backoff.

Example: Retrying API Calls with Exponential Backoff

const fetchWithRetry = async (url, retries = 3, delay = 1000) => {
  try {
    return await fetch(url);
  } catch (error) {
    if (retries > 0) {
      await new Promise((res) => setTimeout(res, delay));
      // Exponential backoff
      return fetchWithRetry(url, retries - 1, delay * 2); 
    }
    throw error;
  }
};

Enter fullscreen mode Exit fullscreen mode

For non-critical failures (e.g., a sidebar not loading), show fallback content rather than breaking the entire UI.

Design for Offline & Slow Networks

Not all users have blazing-fast internet. Progressive Web Apps (PWAs) offer offline support, but even regular apps can:

✅ Cache important data (e.g., using IndexedDB or localStorage)
✅ Display last known state when offline
✅ Use lazy loading for non-essential features

Example: Handling Offline Mode Gracefully

const isOnline = navigator.onLine;
return isOnline ? <Dashboard /> : <p>You’re offline. Showing cached data.</p>;

Enter fullscreen mode Exit fullscreen mode

Conclusion: Build for the Worst, Deliver the Best

A truly great UI isn’t just about aesthetics—it’s about reliability. By designing with resilience in mind, we create applications that handle failures smoothly, keeping users engaged and frustration-free.

“A smooth sea never made a skilled sailor.” — Franklin D. Roosevelt

So let’s embrace failures, design for them, and build frontends that can withstand the unexpected.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)