How to Fix: Compilation is slow

6 min read

That “built in 1s” message is misleading because it reports when the development server is ready to accept requests, not when your application has fully compiled.

In this issue, running npm run dev appears fast at startup, but the real work happens only after the first request hits a route. The result is a confusing developer experience: the terminal suggests everything is done, while the browser still waits on heavy on-demand compilation.

Understanding the Root Cause

This behavior is common in modern JavaScript tooling that uses lazy compilation or incremental bundling during development. When you start the dev server, the tool initializes its runtime, file watchers, dependency graph, and hot reload pipeline. Once that bootstrap phase finishes, it prints a message that the server is ready.

However, that does not mean every page, route, or module has already been bundled. In many dev setups, compilation happens on demand when you first open a page such as the local app. If the app has a large dependency graph, expensive transforms, slow TypeScript analysis, or heavy server/client boundaries, the first page load can still take a long time even though startup logs looked fast.

Technically, the misleading part comes from conflating two separate milestones:

  • Dev server ready: the process is listening for HTTP requests.
  • Application compiled: requested routes and their dependencies have been fully transformed and bundled.

If the issue report says compilation is slow, the actual problem is often not that startup is lying maliciously, but that the displayed log message does not distinguish between server readiness and route compilation readiness.

Typical contributing factors include:

  • Large shared modules imported by the entry route
  • Expensive Babel, SWC, or TypeScript transforms
  • Huge icon packs or utility libraries imported globally
  • Dynamic route trees that trigger broad dependency resolution
  • Slow file systems, especially in containers or network-mounted environments
  • Antivirus or indexing tools interfering with watcher performance

Step-by-Step Solution

The fix is usually a combination of measuring real compile time, reducing what the first route imports, and clarifying the log semantics for developers.

1. Confirm whether the delay is startup or first-request compilation

Start the dev server:

npm run dev

Then immediately open the local app in a browser and compare:

  • Time until terminal says server is ready
  • Time until the browser receives the first rendered page
  • Any extra terminal logs like “compiling”, “compiled”, or route-specific build output

If startup is fast but the first page is slow, you are dealing with lazy route compilation, not true full-startup compilation.

2. Profile the entry route imports

Inspect the page loaded at startup and remove unnecessary top-level imports. Move non-critical code behind dynamic imports where possible.

// Before: heavy module loaded on first request automatically
import BigEditor from './components/BigEditor'
import LargeChartingLibrary from './components/LargeChartingLibrary'

export default function Page() {
  return <MainScreen />
}
// After: defer heavy features until needed
import dynamic from 'next/dynamic'

const BigEditor = dynamic(() => import('./components/BigEditor'))
const LargeChartingLibrary = dynamic(() => import('./components/LargeChartingLibrary'))

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

This reduces the amount of code the dev server must compile for the first route.

3. Avoid oversized barrel imports

Large index files can force the bundler to scan and include more modules than necessary.

// Less efficient
import { Button, Modal, Table, Chart } from './components'
// Better
import Button from './components/Button'
import Modal from './components/Modal'

This can improve both module graph resolution and incremental rebuild speed.

4. Check TypeScript and lint overhead in development

If type checking or linting is running synchronously with dev startup, it can make the experience feel much slower. Separate concerns when possible so the dev server focuses on serving and compiling.

// package.json
{
  "scripts": {
    "dev": "next dev",
    "typecheck": "tsc --noEmit",
    "lint": "eslint ."
  }
}

Run validation in a separate terminal when investigating slowness:

npm run typecheck
npm run lint

5. Test production build behavior separately

Do not use dev-mode timing alone to judge application compile health. Development mode includes hot reload, debugging hooks, and incremental behavior that production does not.

npm run build
npm run start

If production builds are also slow, the issue may be broader than dev-server messaging.

6. Warm up the route intentionally

If your team expects the first route to be ready before manual testing starts, trigger a local request after boot so the initial route compiles immediately.

# Example idea using a local request after startup
curl http://localhost:3000

This does not solve the underlying cost, but it removes the surprise of seeing a “ready” message before the first route compiles.

7. Improve developer-facing messaging

If you maintain the framework or internal tooling, split logging into explicit phases:

Dev server ready in 1.0s
Waiting for first route request...
Compiling / ...
Compiled / in 8.4s

This is the clearest resolution for the reported issue because it aligns logs with actual runtime behavior. The bug is fundamentally about misleading status output.

If compile time is abnormally high, test outside containers, remote volumes, or virtualization layers. Local file watching performance can dramatically affect dev compilation.

# Compare dependency install freshness
rm -rf node_modules .next
npm install
npm run dev

Also verify your Node.js version matches the framework recommendation.

Common Edge Cases

  • Docker or WSL file system lag: file watching and module invalidation can be much slower than native disk access.
  • Huge monorepo dependency graphs: workspace linking may cause the dev server to traverse far more files than expected.
  • Server-only modules imported into shared code: this can trigger extra transforms or invalid bundling paths.
  • Cache corruption: stale framework caches may exaggerate first-load compile times until cleared.
  • Antivirus scanning: security tools can slow reads and writes in node_modules and build cache directories.
  • Dynamic imports used incorrectly: deferring too much can improve compile speed but hurt runtime UX if critical UI becomes delayed.
  • Misreading HMR logs: hot reload rebuild times and first route compile times are different metrics.

FAQ

Why does the terminal say the app is ready if the page still takes several seconds to open?

Because “ready” usually means the dev server process has started listening. It does not guarantee every route has been compiled yet.

Is this a real performance bug or only a logging problem?

It can be both. The misleading message is a logging and UX issue, but the slow first request may still reveal genuine compile overhead from large imports, slow transforms, or environment bottlenecks.

How do I know whether lazy compilation is the cause?

If startup logs are fast, but the first browser request triggers a long pause and route-specific compile output, then on-demand compilation is the likely cause.

The most practical resolution is to treat this issue as a mismatch between server startup time and first-route compilation time. Optimize the initial route, measure the first request separately, and update logs so developers are not misled by a “built” message that only reflects server bootstrap.

Leave a Reply

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