Loader...
This guide explains how to implement robust error handling for server actions that gracefully handles corporate proxy blocking and network issues.
Corporate proxies often block Next.js server actions, causing:
We've implemented a comprehensive error handling system with:
import { ServerActionErrorBoundary } from "@/components/primitives/server-action-error-boundary";
export function MyForm() {
return (
<ServerActionErrorBoundary>
{/* Your existing form */}
</ServerActionErrorBoundary>
);
}
import { useServerAction } from "@/lib/server-action-wrapper";
export function MyForm() {
const { execute, isLoading, error, clearError } = useServerAction(
myServerAction,
{
timeout: 10000,
retries: 2,
showToast: true,
fallback: () => localFallbackFunction(),
},
);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const result = await execute(formData);
if (result.success) {
// Handle success
}
};
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
{isLoading && <div>Loading...</div>}
{error && <div className="error">{error}</div>}
</form>
);
}
interface ServerActionOptions {
/** Timeout in milliseconds (default: 10000) */
timeout?: number;
/** Number of retry attempts (default: 2) */
retries?: number;
/** Delay between retries in milliseconds (default: 1000) */
retryDelay?: number;
/** Show toast notifications on error (default: true) */
showToast?: boolean;
/** Custom error messages */
errorMessages?: {
network?: string;
proxy?: string;
timeout?: string;
server?: string;
unknown?: string;
};
/** Fallback function when server action fails */
fallback?: () => Promise<ServerActionResult<any>>;
/** Custom loading state handler */
onLoadingChange?: (loading: boolean) => void;
}
For critical forms, implement fallback mechanisms:
// Store data locally when server actions are blocked
async function fallbackSubmission(formData: FormData) {
const data = {
email: formData.get("email"),
name: formData.get("name"),
timestamp: new Date().toISOString(),
method: "fallback",
};
// Store in localStorage
const existing = JSON.parse(
localStorage.getItem("pending_submissions") || "[]",
);
existing.push(data);
localStorage.setItem("pending_submissions", JSON.stringify(existing));
return {
success: true,
data,
message: "Data saved locally. Will sync when connection is restored.",
};
}
// Use with server action
const { execute } = useServerAction(myServerAction, {
fallback: () => fallbackSubmission(formData),
});
// ❌ This breaks in corporate environments
"use client";
export function OldForm() {
return (
<form action={myServerAction}>
<input name="email" />
<button type="submit">Submit</button>
</form>
);
}
// ✅ Handles proxy blocking gracefully
"use client";
import { ServerActionErrorBoundary } from "@/components/primitives/server-action-error-boundary";
import { useServerAction } from "@/lib/server-action-wrapper";
export function NewForm() {
const { execute, isLoading, error } = useServerAction(myServerAction, {
timeout: 8000,
retries: 2,
fallback: () => localFallback(),
errorMessages: {
proxy: "Corporate firewall blocked this request. Data saved locally.",
},
});
const handleSubmit = async (e) => {
e.preventDefault();
const result = await execute(new FormData(e.target));
if (result.success) {
toast.success("Submitted successfully!");
}
};
return (
<ServerActionErrorBoundary>
<form onSubmit={handleSubmit}>
<input name="email" />
<button type="submit" disabled={isLoading}>
{isLoading ? "Submitting..." : "Submit"}
</button>
{error && <div className="error">{error}</div>}
</form>
</ServerActionErrorBoundary>
);
}
If you prefer not to use the hook:
import { withServerActionHandling } from "@/lib/server-action-wrapper";
export function ManualForm() {
const handleSubmit = async (formData: FormData) => {
const wrappedAction = withServerActionHandling(myServerAction, {
timeout: 10000,
retries: 1,
fallback: () => localFallback(formData)
});
const result = await wrappedAction(formData);
if (result.success) {
// Handle success
} else {
// Handle error - result.errorType tells you what went wrong
console.log('Error type:', result.errorType);
}
};
return (
<ServerActionErrorBoundary>
<form action={handleSubmit}>
{/* Form content */}
</form>
</ServerActionErrorBoundary>
);
}
Detect and adapt to corporate environments:
import { useDetectCorporateEnvironment } from "@/components/primitives/server-action-error-boundary";
export function AdaptiveForm() {
const { isCorporate, isChecking } = useDetectCorporateEnvironment();
return (
<div>
{!isChecking && isCorporate && (
<div className="warning">
Corporate network detected. Some features may be limited.
</div>
)}
<ServerActionErrorBoundary>{/* Your form */}</ServerActionErrorBoundary>
</div>
);
}
The system categorizes errors into specific types:
enum ServerActionError {
NETWORK_ERROR = "NETWORK_ERROR", // Connection issues
PROXY_BLOCKED = "PROXY_BLOCKED", // Corporate proxy blocking
TIMEOUT = "TIMEOUT", // Request timeout
SERVER_ERROR = "SERVER_ERROR", // 5xx server errors
UNKNOWN = "UNKNOWN", // Other errors
}
Simulate corporate proxy blocking:
// In browser dev tools console:
// Block server actions by intercepting fetch
const originalFetch = window.fetch;
window.fetch = function (url, options) {
if (url.includes("/_next/static/chunks/") || options?.method === "POST") {
return Promise.reject(new Error("403 Forbidden - Corporate proxy"));
}
return originalFetch.apply(this, arguments);
};
Track proxy blocking in your analytics:
// In your error boundary onError callback
onError: (error, errorInfo) => {
if (isProxyBlockingError(error)) {
analytics.track("proxy_blocking_detected", {
userAgent: navigator.userAgent,
hostname: window.location.hostname,
error: error.message,
});
}
};
Set up global defaults in your app:
// _app.tsx or layout.tsx
import { ServerActionErrorBoundary } from "@/components/primitives/server-action-error-boundary";
export default function Layout({ children }) {
return (
<ServerActionErrorBoundary
onError={(error, errorInfo) => {
// Global error tracking
console.error('Global server action error:', error);
}}
>
{children}
</ServerActionErrorBoundary>
);
}
This solution provides a robust, user-friendly way to handle corporate proxy blocking while maintaining full functionality for users on unrestricted networks.