DEV Community

Cover image for How to Fix "Cannot Access '[variable]' Before Initialization" in Next.js
Code Wrangler
Code Wrangler

Posted on

How to Fix "Cannot Access '[variable]' Before Initialization" in Next.js

Recently, I was working on a project using Next.js with the App Router, and I encountered an error I had never seen before, even after years of working with Next.js. At first, I thought it was a simple issue. But after multiple attempts to fix it, the error persisted and ended up delaying our production deployment.

In this article, I’ll break down what causes the "Cannot access '[variable]' before initialization" error, where it typically shows up in Next.js, and how I finally resolved it.

What Causes This Error?

First, it's important to point out:

This is not a Next.js-specific error. It’s a JavaScript runtime error that typically shows up when you're dealing with ES module imports, especially when there's a circular dependency involved.

Basically, the error happens when one module tries to access a variable or function from another module before it’s finished initializing often due to circular imports.

Why This Error Is So Hard to Track

One of the most frustrating things about this error is that it doesn’t happen at build time. It shows up at runtime when everything’s already bundled and Next.js is collecting page data. In my case, the error wasn’t even pointing to anything obvious. It was buried somewhere inside the generated code, making it extremely difficult to figure out where the issue was actually coming from.

I even tried using madge to visualize circular imports:

npx madge --circular src/
Enter fullscreen mode Exit fullscreen mode

But it didn’t detect any cycles - which honestly made things more frustrating.

The Setup That Caused It (My Case)

In my project, I had a schema-helper.ts file where I defined some shared columns and constants like:

// schema-helper.ts
export const id = varchar("id", { length: 36 })
  .primaryKey()
  .$defaultFn(() => generateUniqueId());

export const userId = varchar("user_id", { length: 36 })
  .notNull()
  .references(() => users.id, { onDelete: "cascade" });

export const createdAt = timestamp("created_at").defaultNow();
export const updatedAt = timestamp("updated_at").defaultNow().onUpdateNow();

export const postTypeEnum = ["general", "product", "service", "event"] as const;
export const postStatusEnum = [
  "draft",
  "published",
  "archived",
  "deleted",
] as const;
Enter fullscreen mode Exit fullscreen mode

I was using these shared fields across different schema files:

// posts.ts
export const posts = mysqlTable("posts", {
  id,
  userId,
  type: mysqlEnum("type", postTypeEnum).notNull(),
  ...createdAt,
  updatedAt,
});
Enter fullscreen mode Exit fullscreen mode
// medias.ts
export const medias = mysqlTable("media", {
  id,
  userId: userId,
  type: mysqlEnum("type", mediaType).notNull(),
  ...createdAt,
  updatedAt,
});
Enter fullscreen mode Exit fullscreen mode

Seemed neat and DRY, right? But behind the scenes, userId was referencing the users table, and users.ts was indirectly importing one of the other schema files through a service or handler.

That’s how I ended up with a hidden circular import, and madge couldn’t catch it.

Circular Import Diagram

How I Finally Solved It

After hours of debugging and rethinking the structure, the solution turned out to be straightforward:

I moved the externally declared columns (like userId) out of the shared schema-helper.ts file, and defined them directly inside each schema file that used them.

So instead of this:

// ❌ in schema-helper.ts
export const userId = varchar("user_id", { length: 36 })
  .notNull()
  .references(() => users.id, { onDelete: "cascade" });
Enter fullscreen mode Exit fullscreen mode

I did this:

// ✅ directly inside posts.ts
userId: varchar("user_id", { length: 36 })
  .notNull()
  .references(() => users.id, { onDelete: "cascade" });
Enter fullscreen mode Exit fullscreen mode

I repeated that process for all my schema files (posts, media, likes, etc.), and once I got rid of the import loop, the error disappeared completely.

Final Thoughts

This bug was incredibly frustrating - not just because of the error itself, but because it didn’t surface in any obvious way. There were no TypeScript errors. No clear runtime stack trace. No helpful warning from the build system.

It’s one of those situations where your code is technically correct, but structurally flawed.

If you’re running into this error in your Next.js (or really, any Node/TypeScript) project, especially when using shared helpers or modularized schemas:

  • Look out for circular imports, even indirect ones
  • Avoid referencing entities from other modules inside shared files
  • Move dependent code closer to where it's used

And if all else fails, start simplifying the structure, even if it means a little duplication in the short term. It might just save your sanity.

Let me know if you want to dive deeper into this or need help untangling something similar in your project. I’ve been there.

And don't forget to follow me on X @CodeWithVick

Top comments (0)