πŸ›οΈ

Shopify Store Design

✍️

WordPress Websites

⚑

Custom Web Apps

πŸ“ˆ

Meta Ads Marketing

Partner NetworkWorkPricingAboutContact
Get Started β†’
πŸ“žCallπŸ“‹Get Quote
My Admin Dashboard Returned a 404... But the Route Existed
Engineering Journal #005Intermediate

My Admin Dashboard Returned a 404... But the Route Existed

7 min readBy Ajay Thakkar
Project:Webshastraa CRM
Stack:
Next.js 16ReactTypeScriptJavaScript

I spent hours debugging middleware, redirects, and server routing before discovering the real culprit behind a missing page: a six-line JavaScript Temporal Dead Zone mistake that completely crashed the React component before it could render.

When a Next.js 404 route exists, it breaks every logical assumption you have about web development. You check the file tree, you verify the URLs, and you review the deployment logs, but the browser stubbornly insists the page cannot be found. This exact scenario happened to me while building the administrative interface for the Webshastraa CRM.

I spent hours tearing apart the application architecture, convinced that Vercel, the Next.js App Router, or my authentication middleware had catastrophically failed. The reality was much more humbling. The entire routing infrastructure was functioning flawlessly. The real culprit was a fundamental JavaScript language quirk sitting exactly six lines too low in a single component.

This engineering journal details the anatomy of a "fake 404," the mechanics of the JavaScript Temporal Dead Zone (TDZ), and why diagnosing based solely on browser symptoms is a dangerous debugging trap.

---

1. The Weirdest 404 I've Seen

The user journey was supposed to be simple. I opened my browser and navigated to the root admin path:

[https://www.webshastraa.in/admin](https://www.webshastraa.in/admin)

The server-side redirect logic executed exactly as expected, capturing the request and pushing the user to the default leads dashboard:

/admin  β†’  (Redirect)  β†’  /admin/leads

Then, the screen flashed, and the browser rendered the default Next.js fallback screen:

404 | This page could not be found.

At first glance, this looked like a textbook routing problem. My immediate thoughts were purely infrastructural:

  • Maybe the redirect in next.config.js was misconfigured.
  • Maybe Next.js wasn't generating the page during the build step.
  • Maybe the edge middleware was blocking the request and incorrectly throwing a Not Found response.

Everything pointed toward a failure in the routing layer. The problem? The route actually existed.

---

2. The Investigation Pipeline

Like most developers confronted with an unexpected 404, I started checking the obvious infrastructural components. When debugging a complex Next.js 16 application, you have to eliminate variables systematically.

Was the redirect broken?

No. The browser correctly navigated to /admin/leads. I monitored the network tab in Chrome DevTools. The HTTP 307 Temporary Redirect fired perfectly, and the URL in the address bar updated correctly. The router was doing its job.

Was middleware blocking access?

No. I placed exhaustive console.log statements inside the middleware.ts file to track the request lifecycle. The authentication token was verified correctly. The middleware successfully passed the request down to the actual route handler without terminating the response early.

Did the page actually exist in the filesystem?

Yes. The Next.js App Router relies on strict filesystem-based routing. I verified the directory structure multiple times to ensure there were no typos, missing folders, or case-sensitivity mismatches between my local machine and the deployment environment.

app/
 └── admin/
      └── leads/
           └── page.tsx  <-- The file was definitely here.

No missing folders. No typos. No routing mistakes. The Next.js router should have matched /admin/leads to this exact page.tsx file and rendered the dashboard. Everything looked perfect.

---

3. The Real Bug: A JavaScript Syntax Trap

After exhausting every possible routing configuration, I finally opened app/admin/leads/page.tsx to look at the actual React code. The actual problem had absolutely nothing to do with Next.js, React, or routing.

It was pure, vanilla JavaScript.

Inside the dashboard component, I was filtering a list of incoming CRM leads. The code looked something similar to this:

1export default function LeadsDashboard() {
2  // 🚨 THE FATAL FLAW: Using a variable before it is initialized
3  const filteredLeads = useMemo(() => {
4    return leads.filter(lead => lead.status === 'NEW');
5  }, [leads]);
6
7  // The state declaration happens AFTER the hook tries to use it
8  const [leads, setLeads] = useState([]);
9
10  return (
11    <DashboardLayout>
12       <LeadTable data="{filteredLeads}"/>
13    </DashboardLayout>
14  );
15}
16

Can you spot the error? The useMemo() hook references the leads variable in its dependency array and its callback function. However, the leads variable is not declared until the very next line via the useState hook.

In JavaScript, variables declared with const or let live inside a state called the Temporal Dead Zone (TDZ). This means that while the JavaScript engine knows the variable exists in the block scope (due to hoisting), it completely forbids you from accessing or referencing that variable until the execution thread actually passes over its declaration line.

If you attempt to touch the variable prematurely, JavaScript does not gracefully return undefined. It violently throws a fatal exception:

ReferenceError: Cannot access 'leads' before initialization

The React component was crashing instantly, before it could return any JSX, before it could hydrate, and before it could paint the screen.

---

4. Why a Component Crash Looked Like a 404

This is the exact mechanic that made the bug so incredibly confusing. The route wasn't missing. The server matched the route. The redirect succeeded.

However, because the page component crashed violently during initialization (specifically during Server-Side Rendering or initial React hydration), the Next.js rendering engine aborted the process. Because I had not yet implemented a granular error.tsx boundary file for the /admin route segment, the framework didn't know how to gracefully handle the fatal ReferenceError.

In some deployment environments and specific build configurations, an unhandled fatal error at the very root of a route segment can cause the server to drop the request entirely or fallback to the nearest generic handlerβ€”which, in this specific isolated case, manifested visually as a missing page or a default 404-like error screen to the end user.

The symptom (a missing page) pointed squarely toward routing. The root cause (a ReferenceError) lived deep inside the component's memory allocation.

---

5. The Fix: Escaping the Temporal Dead Zone

The solution was almost embarrassingly simple. To fix a Temporal Dead Zone error, you simply move the state declaration above anything that references it. Execution flows from top to bottom.

1export default function LeadsDashboard() {
2  // 1. Declare the state first (Variable is initialized)
3  const [leads, setLeads] = useState([]);
4
5  // 2. Now the hook can safely reference 'leads'
6  const filteredLeads = useMemo(() => {
7    return leads.filter(lead => lead.status === 'NEW');
8  }, [leads]);
9
10  return (
11    <DashboardLayout>
12       <LeadTable data="{filteredLeads}"/>
13    </DashboardLayout>
14  );
15}
16

One small change. Six lines of code shifted upwards. The Next.js dashboard loaded instantly.

I didn't have to change the edge middleware. I didn't have to update the next.config.js redirects. I didn't have to clear the Vercel cache. I just had to respect the fundamental rules of JavaScript execution.

---

6. Deep Dive: Understanding the Temporal Dead Zone

Many newer developersβ€”especially those who started learning after ES6β€”assume that hoisting applies universally to all variables. They assume this code is perfectly valid:

console.log(user); // Attempting to use the variable early

const user = { name: "Admin" };

The JavaScript V8 engine disagrees entirely:
ReferenceError: Cannot access 'user' before initialization

In the old days of JavaScript, if you declared a variable using var, it was "hoisted" to the top of the file and initialized with undefined. You wouldn't get a crash; you would just get a silent, logical bug because your data was missing.

When const and let were introduced in ES6, the ECMAScript committee wanted to eliminate these silent bugs. They designed the Temporal Dead Zone. When a block of code runs, JavaScript scans it and hoists the const and let declarations to the top of the block, but it leaves them uninitialized. The time between the start of the block and the moment the engine executes the declaration is the TDZ.

React hooks do not grant you immunity from this rule. Hooks are just ordinary JavaScript function calls executed sequentially. If useMemo tries to read leads before useState initializes it, the application will crash.

---

7. Preventing the "Fake 404": Error Boundaries

The biggest architectural lesson here wasn't about moving a variable up six lines; it was about preventing application crashes from masking themselves.

To stop a JavaScript error from masquerading as a missing page, you must implement Error Boundaries. In the Next.js App Router, this is done by adding an error.tsx file alongside your page.tsx file.

1// app/admin/leads/error.tsx
2'use client' // Error components must be Client Components
3
4export default function LeadsError({ error, reset }) {
5  return (
6    <div className="p-10 bg-red-950 text-red-500 rounded-lg">
7      <h2>Something went wrong in the Dashboard!</h2>
8      <p className="font-mono mt-4">{error.message}</p>
9      <button onClick={() => reset()} className="mt-6 bg-red-800 text-white p-2">
10        Try again
11      </button>
12    </div>
13  );
14}
15

If I had this error.tsx file in place originally, the browser would not have shown a confusing 404. It would have instantly rendered this red UI box specifically stating: Cannot access 'leads' before initialization. I would have fixed the bug in exactly 10 seconds instead of wasting two hours debugging my middleware logic.

---

8. Conclusion: The Psychology of Debugging

This bug served as a stark reminder of one of the oldest, most painful lessons in software engineering: The first thing that looks broken usually isn't.

When the browser rendered "404", my brain instantly formed a hypothesis based purely on the visible symptom. I assumed the router was broken. I assumed the middleware was flawed. I assumed the redirect was configured improperly. All of those complex, highly engineered infrastructural systems were entirely innocent.

The real bug was a basic JavaScript variable sitting exactly six lines too low in a text editor.

When debugging, never trust the symptom blindly. Isolate the environment, implement aggressive error boundaries, and always remember that beneath the magic of React, Next.js, and Vercel, you are still bound by the strict, unforgiving rules of vanilla JavaScript.

---

---

πŸ’‘ Key Engineering Takeaways

A 404 Not Found error does not always mean the route is missing; unhandled component crashes can mimic routing failures. React hooks follow normal JavaScript execution order; 'const' and 'let' variables cannot be accessed before declaration. The Temporal Dead Zone (TDZ) causes ReferenceErrors that will instantly crash server-side or client-side renders. Always implement robust error.tsx boundaries in the Next.js App Router to catch component crashes and prevent fake 404s.

Frequently Asked Questions

What is the JavaScript Temporal Dead Zone (TDZ)?

The Temporal Dead Zone (TDZ) is the specific period of time during runtime execution between entering a block scope and actually reaching the declaration of a `let` or `const` variable. Accessing the variable during this period throws a fatal `ReferenceError`.

Why did Next.js show a 404 if the route existed?

The route itself existed perfectly within the file system. However, the page component threw a fatal exception during initialization before React could successfully render it. Without a proper error boundary (`error.tsx`) catching the crash, the server-side rendering process aborted, which caused the application to present a fallback error state that mimicked a routing failure.

Does this Temporal Dead Zone issue only happen with React?

No. The TDZ is a fundamental JavaScript language feature introduced in ES6. React components simply execute standard JavaScript from top to bottom, so the exact same strict rules apply whether you are writing React, Vue, Node.js, or vanilla JavaScript.

How do I avoid this issue in the future?

Always declare your state variables (`useState`) and static variables before any hook (like `useEffect` or `useMemo`) or function that references them. Keep related state declarations organized together near the absolute top of your component body.

What is the biggest architectural lesson here?

Never debug based only on the visible symptom. A routing error can easily be caused by a JavaScript syntax crash. Always utilize Next.js `error.tsx` boundaries so that fatal component errors report their exact stack trace rather than collapsing the entire page into a misleading 404.

Feedback

Was this article helpful?

Continue Reading

Why AI Disabled ESLint Instead of Fixing My CodeNext.js
Β·1 min read

Why AI Disabled ESLint Instead of Fixing My Code

An AI coding agent tried to silence dozens of lint errors by injecting eslint-disable comments instead of fixing the underlying bugs. Discover why treating the linter as the enemy is a dangerous optimization trap in automated software engineering.

Read article β†’
One Shopify CDN Image Broke My Detection EngineNext.js
Β·1 min read

One Shopify CDN Image Broke My Detection Engine

A single Shopify CDN image completely fooled my custom website detection engine into returning a false positive. Discover why this Next.js debugging journey taught me that true architectural server signals must always mathematically outweigh weak third-party media assets in automated auditing tools.

Read article β†’
Everything Returned 200 OK... But My Dropdown Was Still Empty Next.js
Β·1 min read

Everything Returned 200 OK... But My Dropdown Was Still Empty

I spent nearly eight hours debugging an empty dropdown in my Next.js admin panel. The API returned a perfect 200 OK, but the data was missing. Here is why following the data pipeline is critical.

Read article β†’