A Next.js page that works on your computer but returns 500 Internal Server Error on Vercel usually has a server-side failure that only appears in the deployed environment. Common causes include a missing environment variable, an incompatible runtime assumption, or code that fails while rendering on the server.
This guide shows how to find the failing request in Vercel logs, distinguish a build failure from a runtime error, and reproduce an environment-variable issue safely.
Key Takeaways
- A 500 from a deployed page is usually a runtime problem; a failed build is a separate deployment-stage problem.
- Start with the exact deployment and request in Vercel's runtime logs. The browser's generic error page rarely identifies the cause.
- Check that required environment variables exist in the Vercel environment used by that deployment. Updating a variable does not change an already-built deployment; redeploy to apply it.
- Keep server-only dependencies and secrets out of Client Components.
- A successful local build does not prove that deployed requests have the right configuration or runtime.
Find the error in the correct deployment
First, identify the deployment that serves the failing URL. Preview deployments and production deployments can have different environment-variable values and settings.
In the Vercel project, open that deployment and inspect its runtime or function logs. Reproduce the problem while watching the logs, or filter by the approximate time of the failed request. Look for the first relevant exception and its stack trace, not just the final 500 response. Depending on the route, the failure may be logged by a server-rendered page, a Route Handler, or another server-side function.
Also inspect the deployment's build logs. They answer a different question: did the project compile and build? A build that fails normally cannot produce a new successful deployment. If a URL is still returning an error, make sure you are inspecting the deployment actually serving that URL—not a newer failed deployment or a different preview.
If the logs do not show an error, check that you are looking at the right project, deployment, route, and time window. Confirm the response is really a 500: a browser message or a failed client-side request can look like a page failure even when the page document itself returned a different status. A status checker can help confirm the public response code, but it cannot reveal Vercel's internal exception or replace runtime logs; check an HTTP response status.
Build-time errors and runtime errors are different
A build-time error happens while Next.js compiles or prerenders the application. Examples include a syntax error, an invalid import, or code that throws while a statically rendered page is being generated. Find it in the build output. Fixing it requires a successful new build and deployment.
A runtime error happens after deployment, while handling a request. A page may build successfully but throw when it reads a missing environment variable, connects to a service, or executes code that the selected runtime does not support. Find the exception in runtime or function logs.
Some problems can occur at either stage. For example, environment variables may be read during a build-time prerender, or later during a request. The route's rendering behavior determines when the code runs. A successful next build is useful evidence, but it does not confirm that every deployed request has valid configuration.
Reproduce a missing environment variable
This minimal App Router example deliberately returns a server error when GREETING is absent. It makes the configuration failure easy to identify without exposing a secret or environment-variable value in the response.
Create app/page.tsx:
export const dynamic = 'force-dynamic';
export default function Page() {
const greeting = process.env.GREETING;
if (!greeting) {
throw new Error('Missing required environment variable: GREETING');
}
return <main><h1>{greeting}</h1></main>;
}
For local development, add this to .env.local in the project root:
GREETING=Hello from the server
Start the app and visit its home page:
npm run dev
With GREETING set, the page displays “Hello from the server.” To reproduce the failure, temporarily remove the variable from .env.local, restart the development server, and request the page again. The server-side render throws an error. Do not commit .env.local if it contains sensitive values.
To fix the deployed version, add GREETING in the Vercel project’s environment-variable settings for the environment you are testing—Production, Preview, or Development as appropriate—and redeploy. Verify that the failing URL points to that redeployment. The variable must be available to the server; do not prefix a secret with NEXT_PUBLIC_, because that prefix is intended for values exposed to browser-side code.
This is a diagnostic example, not a complete configuration system. For an application with several required variables, validate them in one server-only module and report missing variable names in logs. Never print their values or return secrets in an HTTP response.
Check environment and runtime differences
Environment variables
A value in your local .env.local file is not automatically present in Vercel. Check the exact deployment environment and confirm the variable name matches the code, including capitalization. Also check whether the variable is needed during the build or only during requests: a build-time prerender cannot rely on a value that is available only at runtime.
After changing deployment configuration, create a new deployment so the change is applied. If a preview works but production fails, compare their environment-variable scopes rather than assuming the code differs.
Server-only modules
A server-rendered page can use server-side code, but a Client Component runs in the browser. If a Client Component imports a database client, filesystem package, or module containing secrets, the import may fail during the build or pull server-only code toward the client bundle.
Keep such code behind a server-side boundary. In a Next.js project, importing server-only at the top of a sensitive module helps Next.js flag accidental imports into client code:
import 'server-only';
export function getPrivateServiceKey() {
const key = process.env.PRIVATE_SERVICE_KEY;
if (!key) throw new Error('Missing PRIVATE_SERVICE_KEY');
return key;
}
Call this helper from a Server Component, server action, or server-side handler—not from a module marked with 'use client'. A build error caused by an invalid server/client import is not the same as a 500 from a successfully deployed request; check build output first.
Runtime assumptions
Code that works in a local Node.js server may rely on Node-specific APIs or packages. If a route is configured for an Edge runtime, some Node.js APIs and native dependencies may not be available there. Check the runtime selected for the failing route and the requirements of its dependencies. Do not switch runtimes blindly: make sure the code and package support the runtime you choose.
Compare the Node.js version and dependency installation used locally with the project and deployment configuration. Native modules, filesystem access, and assumptions about a persistent local disk are common places to investigate. A deployed function should not depend on files written to its temporary or deployment filesystem being durable across requests.
A practical debugging sequence
- Reproduce the exact URL and method. Note whether the failing request is the page document, a Route Handler, or a request made by client-side code.
- Open logs for the deployment serving that URL. Find the exception at the same time as the request and follow its stack trace to the failing application code.
- Classify the failure. A build or prerender exception belongs in build logs; an exception during a request belongs in runtime logs.
- Check configuration. Compare the required variable names and the deployment's Preview or Production scope. Confirm whether the value is needed at build time, request time, or both.
- Inspect imports and runtime. Look for server-only modules imported into client code, Node-specific dependencies on an Edge route, and external services that may be unreachable or misconfigured.
- Rebuild and redeploy after a fix. Re-test the same deployment environment and URL. Confirm both the HTTP status and the resulting logs.
Common Mistakes
- Reading only the browser error. A generic 500 page does not identify the server exception; use the deployment's runtime logs.
- Checking the wrong deployment. A preview URL and the production domain can have different settings and serve different builds.
- Assuming local environment files are uploaded. Configure deployment variables in Vercel for the correct environment.
- Changing a variable but not redeploying. Test a new deployment after updating configuration.
- Treating a successful build as proof that requests will work. Runtime configuration, external services, and runtime-specific APIs can still fail later.
- Exposing secrets to fix visibility. Do not return secret values in error messages or add
NEXT_PUBLIC_to a private variable just to make it available. - Switching to Edge without checking dependencies. A runtime change can create new incompatibilities rather than solve the original problem.
Best Practices
- Validate required server-side configuration close to where it is used, and fail with a clear message that names the missing variable but never prints its value.
- Keep server-only modules out of Client Components and keep credentials out of browser-visible variables.
- Test the same environment scope and runtime as the failing deployment.
- Treat build logs and runtime logs as separate diagnostic tools; retain the first meaningful exception from each.
- After changing code or deployment configuration, verify the new deployment rather than relying on an older URL or build.
Conclusion
When a Next.js page works locally but returns a 500 on Vercel, use the failing deployment's runtime logs to find the server exception, then check environment scope, server/client boundaries, and runtime compatibility. Separating build-time failures from request-time failures narrows the search and makes the fix easier to verify.
Documentation note: I could not live-check the current Next.js and Vercel documentation during preparation. Dashboard labels and version-specific behavior may change; confirm those details against the official documentation for your project’s versions.
