🚀 Executive Summary

TL;DR: The ‘PageIndex is not a constructor’ error in TypeScript stems from a module system mismatch where ES Modules are compiled but interpreted as CommonJS by Node.js. Solutions involve aligning `tsconfig.json` and `package.json` to either consistently use CommonJS or fully embrace modern ES Modules with `nodenext` for proper class instantiation.

🎯 Key Takeaways

  • The `TypeError: PageIndex is not a constructor` typically indicates a module system mismatch, not a logic error in the class itself.
  • Node.js often interprets compiled ES Modules as CommonJS, leading to imports resolving to an object like `{ default: MyClass }` instead of the class directly.
  • Setting `”module”: “commonjs”` and `”esModuleInterop”: true` in `tsconfig.json` is a quick fix to force CommonJS output and resolve the mismatch.
  • The modern, permanent solution involves setting `”type”: “module”` in `package.json` and `”module”: “nodenext”`, `”moduleResolution”: “nodenext”` in `tsconfig.json`, which requires explicit file extensions in imports.
  • A temporary ‘nuclear’ option involves explicitly assigning the class to `module.exports` in the class file to bypass module resolution issues in problematic runtime environments.

I build vector less PageIndex for typescript

Struggling with the ‘PageIndex does not have a constructor’ or similar vector-less build errors in TypeScript? Understand the root cause in module resolution and explore three practical, real-world solutions to get your CI/CD pipeline green again.

That ‘PageIndex does not have a constructor’ TypeScript Error? Yeah, We’ve All Been There.

I remember it vividly. It was 2 AM, and we were pushing a critical hotfix for our main user authentication service. Everything worked on our local machines. Everything passed the unit tests. But the moment the build hit the CI runner, `ci-staging-builder-03`, the whole thing fell over with an error that made no sense: TypeError: PageIndex is not a constructor. The junior dev on call was frantically trying to debug the logic, convinced he’d broken the class itself. But I knew that error. It wasn’t a logic bug; it was a ghost in the machine. A ghost that lives between TypeScript’s compiler and Node’s runtime, and it loves to haunt late-night deployments.

So, What’s Actually Going On? The “Why” Behind the Weirdness

Let’s get one thing straight: when you see this error, your code is probably fine. Your PageIndex class almost certainly does have a constructor. The problem isn’t what you wrote; it’s how your code is being compiled and then imported.

At its core, this is a module system mismatch. You’re writing modern TypeScript with ES Modules (using import/export), but the compiled JavaScript is likely being interpreted by Node.js as a CommonJS module (which uses require/module.exports). When this mismatch happens, the thing you are importing isn’t the class itself, but often an object containing your exports, like { default: [class PageIndex] }. So when you try to do new PageIndex(), you’re essentially trying to do new { default: ... }(), which, as TypeScript will scream at you, is not a constructor.

This most often happens when your tsconfig.json settings aren’t perfectly aligned with your project’s execution environment or your package.json settings.

The Fixes: From Duct Tape to a Proper Weld

I’ve seen this fixed a dozen different ways. Here are the three main paths you can take, depending on how much time you have and how “correct” you want to be.

1. The Quick Fix: “Just Make It CommonJS”

This is the fastest way to get your build passing, especially if you’re in a legacy codebase or not ready to fully embrace ES Modules in Node. You basically tell TypeScript, “Hey, forget all that fancy `import` stuff, just compile everything down to the old-school `require()` format that Node has understood for years.”

You do this in your tsconfig.json:

{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs", // <-- This is the magic line
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Why it works: By setting "module": "commonjs", you eliminate the mismatch. TypeScript now outputs code that Node expects by default (in many environments). The esModuleInterop flag helps smooth over any rough edges when you’re importing other libraries that might be in a different format.

Warning: This is a solid fix, but it’s a step away from the modern JavaScript standard. If your project has a "type": "module" in its package.json, this can create a new set of conflicts. You’re kicking the can down the road, but sometimes, that’s what you need to do to get the hotfix out the door.

2. The Permanent Fix: “Embrace the Modules”

This is the “right” way to do it for modern projects. Instead of forcing TypeScript to compile to an older module format, you configure it to output modern JavaScript that aligns with Node’s own ES Module support. This requires a bit more configuration tuning.

First, ensure your package.json signals that you’re a modern module-based project:

{
  "name": "my-awesome-project",
  "version": "1.0.0",
  "type": "module", // <-- This tells Node to treat .js files as ES Modules
  "main": "dist/index.js"
}

Next, update your tsconfig.json to match this modern world:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext", // <-- The key for modern Node ESM support
    "moduleResolution": "nodenext", // <-- How to find modules
    "outDir": "./dist",
    // ... other settings
  }
}

Why it works: Using "module": "nodenext" and "moduleResolution": "nodenext" tells TypeScript to behave exactly how modern Node.js expects. It understands "type": "module", respects conditional exports, and critically, it will force you to use file extensions in your imports (e.g., import { PageIndex } from './PageIndex.js'; even when you’re importing a .ts file). This explicitness is what prevents the runtime confusion.

3. The ‘Nuclear’ Option: “It’s 3 AM and the Build Server is Possessed”

Sometimes, the problem isn’t in your config. It’s in a convoluted build script, a weird Docker layer, or some other CI/CD magic you don’t have time to debug. In these cases, you can bypass the direct import/export confusion by changing how you export your class.

You can force a CommonJS-style export, even within a file that uses ES Modules. It’s ugly, but it works.

In your PageIndex.ts file:

class PageIndex {
  // ... your class implementation
}

// Do your normal export for tooling and type-checking
export { PageIndex };

// Then, add the hack for broken runtimes
// This assigns the class directly to module.exports
module.exports = PageIndex;

Why it works: This is the brute-force method. You’re explicitly telling the Node runtime, “Whatever you think is going on with modules, just ignore it. The one and only thing this file exports is this class.” When the confused importer tries to get the default export, it gets the class directly, and new PageIndex() works again.

Pro Tip: I would only ever use this as a temporary patch to unblock a pipeline. If you check this in, leave a massive comment explaining why it’s there and link to a tech debt ticket to implement Fix #2. Otherwise, the next engineer (who might be you in six months) will have no idea why this voodoo is in the codebase.


Which Path to Choose? A Quick Guide

Here’s how I decide which solution to use:

Scenario Recommended Fix Reason
Critical production hotfix, need it live 10 minutes ago. Fix #1 (CommonJS) It’s the least invasive change with the highest probability of success on older or un-audited build environments.
Starting a new project or doing a major refactor. Fix #2 (Embrace Modules) This is the forward-looking, correct way. It aligns with the entire JavaScript ecosystem’s direction.
CI/CD build is failing with no clear reason and you can’t modify the tsconfig. Fix #3 (‘Nuclear’ Option) When you can’t fix the environment, you sometimes have to make your code more resilient (and a little uglier) to survive in it.

In the end, that 2 AM deployment was saved by Fix #1. We merged the change, the build on `ci-staging-builder-03` went green, and we pushed to prod. The very next morning, I created a ticket to implement Fix #2 and pay down that tech debt. Because as any senior engineer knows, a quick fix is a loan from the future, and the interest is always higher than you think.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ What causes the ‘PageIndex is not a constructor’ error in TypeScript?

This error is primarily caused by a module system mismatch where modern TypeScript (ES Modules) is compiled but then interpreted by Node.js as a CommonJS module, leading to the imported class being wrapped in a default export object (`{ default: MyClass }`) instead of the class itself.

âť“ How does setting `”module”: “nodenext”` compare to `”module”: “commonjs”` in `tsconfig.json`?

`”module”: “commonjs”` forces TypeScript to compile all modules into the CommonJS format, suitable for older Node.js environments. In contrast, `”module”: “nodenext”` configures TypeScript to generate modern ES Modules that align with Node.js’s native ES Module support (when `”type”: “module”` is set in `package.json`), requiring explicit file extensions in imports.

âť“ When should I use the ‘Nuclear Option’ (`module.exports = PageIndex;`)?

The ‘Nuclear Option’ should only be used as a temporary, last-resort patch when facing critical CI/CD build failures due to module resolution issues that cannot be quickly resolved by `tsconfig.json` or `package.json` changes, especially in complex or unmodifiable build environments. It is considered technical debt and should be replaced by a proper fix as soon as possible.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading