Blog
frontend
4 min read

Stop Half-Broken UX: Error Boundary Boundaries in Next.js

The point is simple: place error boundaries on feature boundaries so UX survives failures. In Next.js, combine segment boundaries (error.js) with feature-level boundaries to localize crashes.

Conclusion: put error boundaries on feature boundaries, or UX collapses. One global boundary makes everything fall together. Too many micro-boundaries leave a half-broken screen behind. This is where it splits: decide how far failure is allowed to spread, then place boundaries exactly there.

Background / Problem

Modern web apps are a mesh of moving parts. When one part stops, UI can break in unexpected ways. The real question isn’t whether an error happens — it’s how the experience degrades when it does. That’s why you need fault tolerance (fault tolerance: the property of continuing to operate even when some components fail).

Key Concept

Error Boundaries

In React, an error boundary catches rendering errors and switches to fallback UI. It’s implemented as a class component using componentDidCatch / getDerivedStateFromError. React Error Boundaries

Point: error boundaries aren’t for “hiding errors”. They exist to prevent leaving corrupted UI on screen.

Boundaries in Next.js

Next.js provides error.js to capture rendering errors at the route segment level and enable recovery (e.g., a retry button) inside the same segment. Next.js Error Handling

다이어그램 불러오는 중...

→ Expected result / what changes: failures stay within a segment or feature instead of taking down the whole app. Recovery points become obvious.

Approach

Here’s the point.

  1. Don’t use only one top-level boundary
  • Why: one crash can take down everything.
  • Expected result: failure stays localized.
  1. Don’t wrap every component
  • Why: a “half-broken UI” becomes a completely broken UX.
  • Expected result: you avoid confusing, inconsistent states.
  1. Find feature boundaries and place boundaries there
  • Why: the question “Should this failure take down siblings?” defines the boundary.
  • Expected result: predictable blast radius + predictable fallback UI.

Implementation (Code)

1) Feature-level Error Boundary

You can implement this directly with a React class, or use a library such as react-error-boundary. react-error-boundary

typescript
// components/FeatureErrorBoundary.tsx
import React from "react";

type Props = {
  children: React.ReactNode;
  fallback: React.ReactNode;
};

type State = { hasError: boolean };

export class FeatureErrorBoundary extends React.Component<Props, State> {
  state: State = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: unknown) {
    // You can wire logging/reporting here.
    // (depends on environment/policy)
    console.error(error);
  }

  render() {
    if (this.state.hasError) return this.props.fallback;
    return this.props.children;
  }
}

→ Expected result / what changes: a rendering error switches only that feature area to a fallback.

2) Wrap by “feature boundary” in Next.js

Browser-only APIs (window, document) should run in a Client Component so execution context stays explicit.

typescript
// app/(home)/page.tsx
import { FeatureErrorBoundary } from "@/components/FeatureErrorBoundary";

function Trends() {
  return <aside>...</aside>;
}

function WhoToFollow() {
  return <section>...</section>;
}

export default function HomePage() {
  return (
    <main>
      <FeatureErrorBoundary fallback={<div>타임라인을 불러오지 못했습니다.</div>}>
        <Timeline />
      </FeatureErrorBoundary>

      <FeatureErrorBoundary fallback={<div>추천을 불러오지 못했습니다.</div>}>
        <WhoToFollow />
      </FeatureErrorBoundary>

      <FeatureErrorBoundary fallback={<div>트렌드를 불러오지 못했습니다.</div>}>
        <Trends />
      </FeatureErrorBoundary>
    </main>
  );
}

→ Expected result / what changes: if the recommendation section crashes, the timeline stays alive. Users instantly understand what failed.

3) Route segment boundary: error.js

Segment-level fallback replaces “the segment area”, not the entire app.

typescript
// app/(home)/error.tsx
"use client";

export default function Error({ reset }: { reset: () => void }) {
  return (
    <div>
      <p>이 섹션에서 문제가 발생했습니다.</p>
      <button onClick={() => reset()}>다시 시도</button>
    </div>
  );
}

→ Expected result / what changes: users can retry in place instead of hard-refreshing.

Verification Checklist

Add throw new Error() inside a feature component: only that feature switches to fallback.
The fallback UI keeps layout stable and clearly communicates the failure.
With error.js, errors stay within the segment instead of propagating. Next.js Error Handling
Browser-only APIs run only in Client Components.

Common Mistakes / FAQ

Q. Isn’t one top-level boundary enough? Usually not. One failure takes everything down — including areas that don’t need to fail.

Q. Then should I add boundaries everywhere? No. Micro-boundaries can leave “half-broken” UI states that confuse users. Feature boundaries are the sweet spot.

Q. Do error boundaries catch everything? They focus on rendering errors. Event handlers and some async flows may need separate handling. React Error Boundaries

Summary

Error boundaries aren’t about “more”, they’re about “right”. Define feature boundaries first, and UX won’t collapse when failures happen. In Next.js, combine segment boundaries (error.js) with feature boundaries to make recovery points obvious. Testing is simple: break things on purpose and confirm what survives.

Conclusion

Fault tolerance isn’t optional — it becomes mandatory as apps grow. One immediate action: keep asking “Should this failure take down siblings?” and place the boundary where the answer is no.

References

Related Posts