How to Fix: Need build time checks for next plugin in TS

6 min read

Next.js TypeScript plugin checks do not fail builds by default, and that is exactly why invalid route export values can slip through CI while your IDE looks perfectly happy.

In current Next.js workflows, the TypeScript plugin for Next.js improves editor feedback for route segment exports such as revalidate, dynamic, and related App Router conventions. The catch is that these checks are often editor-only diagnostics. If your team expects the same validation during build time, you can end up shipping invalid configuration values unless you add explicit enforcement.

The issue discussed in this Next.js discussion comes from a mismatch between what the IDE plugin knows and what the build pipeline actually enforces.

Understanding the Root Cause

Next.js ships a TypeScript language service plugin that teaches your editor about framework-specific rules. That includes validating special exports in App Router files, for example:

export const revalidate = 60

Inside the editor, the plugin can flag invalid values or unsupported patterns because the TypeScript server is running with Next.js plugin awareness. However, the build path is different:

  • IDE diagnostics come from the TypeScript language service.
  • Build diagnostics come from Next.js compilation, TypeScript compilation, linting, and framework validation.

If a rule exists only in the language service plugin and is not mirrored by a corresponding build-time validation step, then next build may not fail even though the editor reports a problem.

This usually happens for one of these reasons:

  • The check is implemented as a developer experience enhancement, not a hard compiler error.
  • tsc does not execute framework-specific semantic rules from the Next.js plugin the same way your IDE does.
  • Your CI runs next build and maybe tsc --noEmit, but neither reproduces all editor plugin diagnostics.

So the real bug is not that TypeScript is broken. The real problem is a validation gap between local editor tooling and automated builds.

Step-by-Step Solution

The most reliable fix today is to treat route export validation as a build contract instead of assuming the Next.js TypeScript plugin will enforce it in CI automatically.

1. Keep the Next.js TypeScript plugin enabled

First, make sure your project is actually using the plugin in tsconfig.json so developers still get fast feedback in the editor.

{
  "compilerOptions": {
    "plugins": [
      { "name": "next" }
    ]
  }
}

This does not solve build-time enforcement by itself, but it preserves the IDE safety net.

2. Add a strict TypeScript check to CI

Run a dedicated TypeScript pass in your pipeline:

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "build": "next build",
    "ci": "npm run typecheck && npm run build"
  }
}

This catches normal TypeScript issues, even though it may still miss some Next.js plugin-only diagnostics.

3. Create explicit type-safe wrappers for route segment exports

If values like revalidate are critical, define them through a typed helper so invalid values become regular TypeScript errors.

// lib/next-route-config.ts
export type RevalidateValue = number | false

export function defineRevalidate(value: RevalidateValue): RevalidateValue {
  return value
}

Then use it in route files:

// app/page.tsx
import { defineRevalidate } from '@/lib/next-route-config'

export const revalidate = defineRevalidate(60)

export default function Page() {
  return <div>Home</div>
}

If a developer passes an obviously invalid type, TypeScript now fails during type checking:

export const revalidate = defineRevalidate('60')

That converts a framework-specific convention into a standard compile-time type contract.

4. Add custom validation for rules TypeScript cannot express well

Some Next.js export constraints are not just about primitive types. They may involve literal unions, file conventions, or environment-sensitive restrictions. For those, add a small validation script that scans route files before build.

// scripts/validate-next-exports.mjs
import fs from 'node:fs'
import path from 'node:path'

const appDir = path.join(process.cwd(), 'app')
const invalid = []

function walk(dir) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const fullPath = path.join(dir, entry.name)
    if (entry.isDirectory()) {
      walk(fullPath)
      continue
    }

    if (!/\.(ts|tsx|js|jsx)$/.test(entry.name)) continue

    const source = fs.readFileSync(fullPath, 'utf8')

    const match = source.match(/export\s+const\s+revalidate\s*=\s*([^\n;]+)/)
    if (match) {
      const value = match[1].trim()
      const isNumber = /^\d+$/.test(value)
      const isFalse = value === 'false'
      if (!isNumber && !isFalse) {
        invalid.push(fullPath)
      }
    }
  }
}

if (fs.existsSync(appDir)) {
  walk(appDir)
}

if (invalid.length) {
  console.error('Invalid Next.js route export values found:')
  for (const file of invalid) {
    console.error(`- ${file}`)
  }
  process.exit(1)
}

console.log('Next.js route export validation passed.')

Wire it into CI:

{
  "scripts": {
    "validate:next-exports": "node scripts/validate-next-exports.mjs",
    "typecheck": "tsc --noEmit",
    "build": "next build",
    "ci": "npm run validate:next-exports && npm run typecheck && npm run build"
  }
}

This is the practical workaround when the framework plugin provides editor intelligence but not guaranteed build enforcement.

5. Use ESLint as an additional guardrail

If your team already relies on linting in pull requests, consider a custom ESLint rule or local rule pack to validate segment config exports. That gives you another automated gate before merge.

{
  "scripts": {
    "lint": "next lint",
    "ci": "npm run validate:next-exports && npm run typecheck && npm run lint && npm run build"
  }
}

For teams with larger codebases, lint rules are often easier to maintain than ad hoc shell checks.

6. Fail fast in monorepos

If you use a monorepo, run the validation in the specific app package before expensive builds start. Example:

# pnpm
pnpm --filter web-app run validate:next-exports
pnpm --filter web-app run typecheck
pnpm --filter web-app run build

This prevents hidden route export mistakes from reaching later stages of the pipeline.

Common Edge Cases

  • Computed export values: If revalidate is assigned through variables or function calls, simple regex validation may miss it. Use typed helpers or AST-based validation for stronger guarantees.
  • Mixed JS and TS files: If some route files are JavaScript, TypeScript-only checks will not protect them fully. Include those files in custom validation.
  • Monorepo tsconfig inheritance: The next plugin may be defined in one config but not applied where developers expect. Verify the active tsconfig.json for each app.
  • Editor appears correct, CI passes incorrectly: This is the exact symptom of plugin-only diagnostics. Do not assume editor errors automatically map to build failures.
  • Literal constraints: Some exports require specific string literals or booleans. A broad helper type may be too permissive unless you model the exact allowed union.
  • Version differences: Next.js behavior can change across releases. If you are testing framework validation, pin your version and verify against the release actually used in production.

FAQ

Why does VS Code show an error if next build succeeds?

Because VS Code can use the Next.js TypeScript plugin through the language service, while next build may not enforce that exact diagnostic as a hard build error. Editor feedback and CI validation are separate systems.

Can tsc --noEmit replace next build for this problem?

No. tsc --noEmit is useful for catching regular TypeScript type errors, but it does not fully replace framework-specific validation done by Next.js. You usually need both, plus custom checks if the rule is plugin-only.

What is the best production-safe workaround right now?

The safest approach is a combination of typed helper functions, CI typecheck, and a custom validation script or lint rule for special Next.js exports. That gives you deterministic build failures instead of relying only on IDE diagnostics.

If your goal is parity between the Next.js IDE plugin and build-time enforcement, the key takeaway is simple: treat plugin diagnostics as helpful hints, then add your own hard checks in CI until the framework exposes the same validation during next build.

Leave a Reply

Your email address will not be published. Required fields are marked *