You can build features fast when you skip loading states, error handling, and design consistency. But those shortcuts compound. After 18 months of feature development on Boottify, we had 275,816 lines of code across 212 pages and 400+ API routes — with loading states on maybe 20% of pages, error boundaries on none, hardcoded hex colors everywhere, and console.log scattered across production code. We stopped feature work for a week and audited everything.
THE SCALE OF THE AUDIT
Before we could fix anything, we needed to measure the scope:
- 275,816 lines across
src/ - 212 page components (route segments with
page.tsx) - 400+ API route handlers
- 30 loading.tsx files out of 212 pages (14% coverage)
- 0 error.tsx files in route groups
- 3,796 hardcoded hex color values across 148 .tsx files
- ~147 console.log statements in infrastructure code
151 LOADING STATES: 100% COVERAGE
Every Next.js page should have a loading.tsx file. Without it, navigation to that page shows nothing until the server component finishes fetching data — which can take 200ms to 3 seconds depending on the query. Users see a blank screen and think the app is broken.
Our loading skeleton pattern uses a consistent pulse animation:
// Example: src/app/(control-center)/admin/blog/loading.tsx
export default function Loading() {
return (
<div className="space-y-6">
{/* Page header skeleton */}
<div className="flex items-center justify-between">
<div className="h-8 w-48 rounded bg-white/5 animate-pulse" />
<div className="h-10 w-32 rounded bg-white/5 animate-pulse" />
</div>
{/* Filter bar skeleton */}
<div className="flex gap-3">
<div className="h-10 w-64 rounded bg-white/5 animate-pulse" />
<div className="h-10 w-32 rounded bg-white/5 animate-pulse" />
</div>
{/* Table skeleton */}
<div className="border border-border rounded overflow-hidden">
<div className="h-12 bg-white/5 animate-pulse" />
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="h-16 border-t border-border bg-white/[0.02] animate-pulse"
style={{ animationDelay: `${i * 75}ms` }} />
))}
</div>
</div>
);
}
Key design decisions:
- Staggered animation delays — each row pulses slightly after the previous one, creating a wave effect that feels intentional rather than glitchy
- Layout-matching shapes — the skeleton mirrors the actual page layout (header + filters + table, or header + cards grid, etc.)
- Subtle opacity —
bg-white/5is barely visible on our dark backgrounds, avoiding jarring flashes
We generated 151 loading.tsx files across every route segment. Every page now shows an instant skeleton on navigation, then swaps to the real content when data arrives.
12 ERROR BOUNDARIES ACROSS ROUTE GROUPS
Next.js error boundaries use error.tsx files that catch render errors and API failures within their route segment. Without them, any unhandled error crashes the entire page to the global error boundary — which in our case was a generic "Something went wrong" with no retry option.
We added error boundaries to 12 critical route groups:
// Example: src/app/(control-center)/admin/billing/error.tsx
"use client";
import { useEffect } from "react";
export default function BillingError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log to structured logger in production
console.error("Billing section error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
<div className="text-center space-y-2">
<h2 className="text-xl font-bold text-foreground">
Something went wrong
</h2>
<p className="text-muted-foreground text-sm max-w-md">
There was an error loading the billing section.
This has been logged automatically.
</p>
{process.env.NODE_ENV === "development" && (
<pre className="mt-4 p-3 bg-card rounded text-xs text-red-400 max-w-lg overflow-auto">
{error.message}
</pre>
)}
</div>
<button
onClick={reset}
className="px-4 py-2 bg-primary text-primary-foreground font-medium hover:brightness-110 transition-all"
>
Try again
</button>
</div>
);
}
The error boundaries were added to: admin billing, plans, settings, services, webhooks, marketplace, and client-side billing, marketing, orders, plus auth pages and the control center layout.
3,796 HARDCODED COLORS MIGRATED TO CSS VARIABLES
Our codebase was littered with raw hex values: bg-[#0f131c], text-[#8a8f98], border-[#d2f800]. This made theme changes impossible and visual consistency a manual effort. We migrated all 3,796 instances across 148 files to CSS variable-based Tailwind classes:
| Before (hardcoded) | After (CSS variable) | Count |
|---|---|---|
bg-[#080c14] | bg-background | 312 |
bg-[#0f131c] | bg-card | 487 |
bg-[#121620] | bg-secondary | 203 |
text-[#8a8f98] | text-muted-foreground | 891 |
text-[#d2f800] | text-primary | 634 |
border-[rgba(255,255,255,0.1)] | border-border | 756 |
bg-[#d2f800] | bg-primary | 513 |
This was largely automated with a series of targeted find-and-replace passes, followed by manual review of edge cases where the same hex value was used for different semantic purposes.
CONSOLE.LOG TO STRUCTURED LOGGING
Production code had ~147 console.log statements in infrastructure files — deployment executors, SSL provisioners, Docker operations, email senders. These produced unstructured output that was impossible to filter or search in production logs.
We migrated to a structured logger:
// Before
console.log("Deploying app", appId, "to namespace", namespace);
console.error("Deployment failed:", error.message);
// After
logger.info("Deploying app", { appId, namespace });
logger.error("Deployment failed", { appId, error: String(error) });
The logger outputs JSON in production (for log aggregation) and pretty-printed text in development. Each log entry includes a timestamp, level, message, and structured metadata.
6 CSS ANIMATIONS
We added six reusable CSS animations to globals.css for consistent motion throughout the UI:
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-right {
from { opacity: 0; transform: translateX(16px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 8px rgba(210, 248, 0, 0.3); }
50% { box-shadow: 0 0 20px rgba(210, 248, 0, 0.6); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes blur-in {
from { opacity: 0; filter: blur(4px); }
to { opacity: 1; filter: blur(0); }
}
All animations respect prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
API RESPONSE STANDARDIZATION
API routes had inconsistent response shapes — some returned { data: ... }, others returned { result: ... }, and error responses varied between { error: "message" } and { message: "error" }. We standardized with two helpers:
// Consistent success response
export function apiSuccess<T>(data: T, status = 200) {
return NextResponse.json(data, { status });
}
// Consistent error response with withErrorHandler wrapper
export function withErrorHandler(
handler: (req: NextRequest) => Promise<NextResponse>
) {
return async (req: NextRequest) => {
try {
return await handler(req);
} catch (error) {
if (error instanceof ApiError) {
return NextResponse.json(
{ error: error.message },
{ status: error.status }
);
}
logger.error("Unhandled API error", { error: String(error) });
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
};
}
ACCESSIBILITY IMPROVEMENTS
The audit also covered accessibility:
- Skip links added to all layouts — keyboard users can skip navigation and jump to main content
- ARIA landmarks —
role="main",role="navigation",role="banner"across all layout components aria-current="page"on active navigation items- Focus-visible outlines — custom focus rings using
outline-2 outline-primaryinstead of browser defaults
THE BOTTOM LINE
A week of auditing produced:
- 151 loading.tsx files — 100% page coverage (up from 14%)
- 12 error.tsx boundaries — graceful error handling per route group
- 3,796 color migrations — all hardcoded hex to CSS variables
- ~147 console.log replaced with structured logging
- 6 CSS animations with reduced-motion respect
- API response standardization across all 400+ routes
- Accessibility landmarks and skip links across all layouts
None of this adds features. All of it makes the platform feel polished, debuggable, and maintainable. Technical debt doesn't announce itself — it accumulates silently until the codebase fights you on every change. Dedicated audit sprints are the antidote.



