ui-boundary-helper
v0.1.3
Published
A Next.js development tool that visualizes Server Components, Client Components, Suspense, cache, and Server Actions with interactive UI boundaries.
Readme
ui-boundary-helper
A Next.js App Router development tool that automatically annotates Server Components, Client Components, Suspense boundaries, cache directives, and Server Actions with interactive visual overlays.
See information.md for the meaning of popup fields, render-time measurements, directive labels, and toolbar controls.
See the changelog for changes in upcoming and previous releases.
The boundary toolbar includes a searchable navigator for detected components. Select a boundary to scroll to it and open its popup. Popup render history shows its total observed render count, recent average, and up to ten latest timings.
Install
Use the package manager already used by your project:
# npm
npm install ui-boundary-helper
# pnpm
pnpm add ui-boundary-helper
# Yarn
yarn add ui-boundary-helper
# Bun
bun add ui-boundary-helperTroubleshooting npm install
If npm reports Cannot read properties of null (reading 'matches'), it may have encountered dependency links left by another package manager or a local linked package. This error alone does not indicate a Next.js compatibility problem. See the npm issue for this error.
If your project uses pnpm, Yarn, or Bun, use its matching install command above. For an npm project, try rebuilding the installed dependencies from the project root while keeping package-lock.json:
# macOS / Linux
rm -rf node_modules
npm install
npm install ui-boundary-helperOn Windows PowerShell:
Remove-Item -Recurse -Force node_modules
npm install
npm install ui-boundary-helperWrap the app
Enable automatic annotations in next.config.ts with the package's Next.js integration. It installs a source transform for local .tsx and .jsx files under both Turbopack and webpack:
import type { NextConfig } from "next";
import { withBoundaryDebug } from "ui-boundary-helper/next";
const nextConfig: NextConfig = {};
export default withBoundaryDebug(nextConfig);Then add BoundaryProvider once in the root app/layout.tsx as the first direct child of <body>, wrapping your other providers and app content. The layout remains a Server Component; the provider receives children as a slot.
import { BoundaryProvider } from "ui-boundary-helper";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<BoundaryProvider debug={process.env.NODE_ENV === "development"}>
{children}
</BoundaryProvider>
</body>
</html>
);
}The transform reads local component files and directives as Next.js compiles them, then injects annotations automatically. Hot reload recompiles changed files and refreshes their annotations. Set debug to true or false in the layout to turn annotations on or off. It defaults to enabled in development and disabled in production. The provider adds no layout element, and annotations are drawn in a pointer-transparent overlay. DebugBoundary remains available for a manual override when needed:
import { DebugBoundary } from "ui-boundary-helper";
<DebugBoundary kind="client" name="HeroBanner.tsx">
<HeroBanner />
</DebugBoundary>Automatic labels use boundary type | component-file.tsx, such as Client Component | client-counter.tsx or Server Component | page.tsx. A Server Action is labeled Server Action. If you manually provide detail, it appears as a third field. Client annotations are blue, server annotations are red, cache annotations are green, and Suspense annotations are amber. Nested components receive separate outlines, so a server component containing a client component with a cached server child appears as nested layers. Labels sit above their outlines with visible spacing; the dotted borders and labels overlay the page without changing its layout.
While a boundary's popup is open, its background tint becomes darker to make the selected region stand out. Pinning the popup keeps that highlight active; closing it restores the normal tint. This follows the toolbar's global and per-kind background-color settings.
Use the minimize icon next to the pin icon to collapse popups to just the component name and controls. The minimize and transparency settings apply to every open popup; each popup keeps its own pin state. Click the expand icon to restore all fields. Minimizing works with pinned popups and keeps the boundary highlight active. Boundary visibility, background tint, per-kind visibility and tint, popup minimize, and popup transparency settings are saved in local storage and restored after a page refresh.
Choose Reset UI settings in the boundary toolbar to restore annotation visibility, colors, type filters, popup display, and toolbar appearance to their defaults.
For a region that represents multiple concepts, pass several kinds; their labels and outlines stack above and around the same region:
<DebugBoundary kinds={["cache", "server"]} name="CachedProductData">
<ProductData />
</DebugBoundary>DebugBoundary can receive Server Component output through children. For async server UI, SuspenseBoundary pairs a fallback with the resolved slot:
import { SuspenseBoundary } from "ui-boundary-helper";
<SuspenseBoundary name="ProductReviews" fallback={<ReviewsSkeleton />}>
<ProductReviews />
</SuspenseBoundary>The Next integration reads directives during compilation, not by inspecting runtime component objects. It adds a boundary around each detected local component, including the default page export, named component functions, components passed to wrappers such as memo, and React class render methods. A file with 'use client' is labeled as a Client Component; files without it are labeled as Server Components by default. Function-level 'use cache' directives receive cache and server annotations on that component. <Suspense> boundaries receive a persistent Suspense annotation, with the wrapped component name available on hover; forms using Server Actions receive annotations as well. A Server Action marked with 'use server' is a function, not a Server Component. The server-only and client-only package markers are also supported, but are not directives.
Render-time measurements
Each popup includes a timing value. Component timings measure the component function's execution up to its JSX return; async Server Components include work they await. They do not measure the full React subtree render, network transfer, or browser paint. A cached component reports its cache-generation timing, which can be reused on cache hits rather than measuring the cache lookup. Suspense reports the client-observed fallback wait when the fallback is observed, and Server Actions report the browser-observed submit-to-completion round trip rather than server-only execution time.
How cached component inputs work
Next.js uses the cached function or component and its serializable inputs to determine which result can be reused. Pass every value that can change the result into the cached component. A different input combination gets its own cache entry; a later render with the same inputs can reuse that entry while it is valid.
For example, a product variant can be cached by product ID and color:
type CachedBagProps = {
productId: string;
color: "green" | "blue";
};
async function CachedBag({ productId, color }: CachedBagProps) {
"use cache";
const bag = await getBagVariant(productId, color);
return (
<article>
<img src={bag.imageUrl} alt={`${color} ${bag.name}`} />
<h2>{bag.name}</h2>
<p>Color: {color}</p>
<p>{bag.price}</p>
</article>
);
}("bag-123", "green") and ("bag-123", "blue") are separate entries. Another request for ("bag-123", "green") can reuse the green result. Include every result-changing input, such as size or locale, too. If almost every input combination is unique, caching may have few hits. A cached component's rendered markup and image URL may be reused; the image file itself is handled by the browser or image CDN cache separately. Set an appropriate cache lifetime or invalidate entries when product data changes. See Next.js' use cache documentation for cache keys, serialization, and invalidation.
The React wrapper uses React and React DOM. The optional Next integration uses TypeScript to transform app source files during builds.
Local example
This repository includes a deliberately small Next.js app under examples/next-app to try automatic annotations, the client island, cached server output, Server Actions, and Suspense boundaries.
pnpm install
pnpm --filter ui-boundary-helper build
pnpm --filter next-app-example devFrom the repository root, npm run dev starts the same example app.
Package development
pnpm build
npm pack
npm publish