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/
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;
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,
});
// medias.ts
export const medias = mysqlTable("media", {
id,
userId: userId,
type: mysqlEnum("type", mediaType).notNull(),
...createdAt,
updatedAt,
});
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.
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 sharedschema-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" });
I did this:
// ✅ directly inside posts.ts
userId: varchar("user_id", { length: 36 })
.notNull()
.references(() => users.id, { onDelete: "cascade" });
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)