Node.js 24 Ships Native TypeScript: The End of Build Steps?
It’s finally here. After years of requiring TypeScript compilation before execution, Node.js 24 lets you run .ts files directly. But does this mean you can throw away your bundler? Lets dive in.
The Big Picture
Node.js 24 (recommended LTS as of March 2026) has landed with a feature that developers have wanted for over a decade: native TypeScript support. Using the --experimental-strip-types flag (now enabled by default for .ts files), you can execute TypeScript directly without any compilation step.
Combine this with npm v11s 65% faster installations, and the JavaScript/TypeScript development workflow has fundamentally changed.
# Before: You needed this mess
npm install -D typescript ts-node esbuild
npx tsc
node dist/app.js
# Now: Just this
node app.ts
Thats it. Thats the headline. But theres more nuance you need to understand.
How Native TypeScript Actually Works
The Mechanism: Type Stripping, Not Type Checking
When Node.js executes a TypeScript file directly, it doesnt perform type checking—thats still TypeScripts job. Instead, Node uses a process called type stripping that removes all type annotations, interfaces, and type-only constructs before execution.
// app.ts - This is valid TypeScript
interface User {
name: string;
email: string;
age: number;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
const newUser: User = {
name: "Sarah",
email: "sarah@example.com",
age: 28
};
console.log(greet(newUser));
When Node.js 24 runs this, it internally transforms it to:
// What Node actually executes
function greet(user) {
return `Hello, ${user.name}!`;
}
const newUser = {
name: "Sarah",
email: "sarah@example.com",
age: 28
};
console.log(greet(newUser));
Enabling Native TypeScript
Option 1: Automatic (Node.js 24+)
Just run your .ts file:
node app.ts
Node.js automatically detects .ts extension and enables type stripping. No flags needed.
Option 2: Explicit Flag
node --experimental-strip-types app.ts
Option 3: Package.json Configuration
{
"name": "my-app",
"type": "module",
"scripts": {
"start": "node --experimental-strip-types src/index.ts",
"dev": "node --watch --experimental-strip-types src/index.ts"
}
}
Running TypeScript with ES Modules
If youre using ES modules (import/export syntax), it works seamlessly:
// utils/calculator.ts
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
// main.ts
import { add, multiply } from ./utils/calculator.js;
console.log(5 + 3 =, add(5, 3));
console.log(4 × 7 =, multiply(4, 7));
Run it:
$ node main.ts
5 + 3 = 8
4 × 7 = 28
Comparison: Traditional Build vs Native Execution
Development Workflow Comparison
| Aspect | Traditional Build | Node.js 24 Native |
|---|---|---|
| First execution | Compile then run | Direct execution |
| Hot reload | Rebuild + restart | Node –watch |
| Type errors | Build fails | Still runs (runtime behavior) |
| Dependencies | 5+ npm packages | None extra |
| Startup time | tsc + node | Just node |
The Real Speed Difference
Heres a benchmark comparing workflows:
// benchmark.ts - Simple HTTP server
import http from http;
const server = http.createServer((req, res) => {
res.writeHead(200, { Content-Type: text/plain });
res.end(Hello from Node.js 24!n);
});
server.listen(3000, () => {
console.log(Server running at http://localhost:3000/);
});
Traditional workflow (2025):
$ time npx tsc && node dist/benchmark.js
real 0m2.341s # tsc compilation time
$ time node --watch dist/benchmark.js # Rebuild on change
Node.js 24 workflow (2026):
$ time node --watch benchmark.ts
real 0m0.089s # Just Node startup
$ time node --watch benchmark.ts # Instant hot reload
The difference? ~26x faster startup in development.
Migration Guide: Existing Projects
Step 1: Install Node.js 24
# Using nvm (recommended)
nvm install 24
nvm use 24
# Verify
node --version
# v24.0.0
Step 2: Update package.json
{
"scripts": {
"start": "node server.ts",
"dev": "node --watch server.ts",
"build": "tsc", // Keep for production builds
"type-check": "tsc --noEmit"
},
"engines": {
"node": ">=24.0.0"
}
}
Step 3: Configure TypeScript for Production
You still need TypeScript compilation for production deployments—but now its only for production:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"stripInternal": true
}
}
Step 4: Handle Environment-Specific Code
// config.ts
const isProduction = process.env.NODE_ENV === production;
export const config = {
// This still works in native TypeScript
logLevel: isProduction ? warn : debug,
// Conditional types - handled by type stripping
...(isProduction && {
apiUrl: https://api.production.com
})
};
npm v11: The 65% Faster Installation
Node.js 24 ships with npm v11, which brings significant performance improvements:
# npm v10 (2025)
$ time npm install
real 0m8.432s
# npm v11 (2026)
$ time npm install
real 0m2.951s
Key improvements:
- Parallel dependency resolution – dependencies install concurrently
- Improved caching – smarter detection of cached packages
- Reduced metadata overhead – less package.json parsing
# New npm 11 commands
npm install --parallel # Force parallel installs
npm install --prefer-offline # Use cached packages first
Limitations and Caveats
What Native TypeScript Doesnt Do
❌ No type checking at runtime
// This runs without error in Node.js 24
const user: User = "not a user"; // No runtime error!
❌ No type erasure at compile time for dist/
Type stripping happens at runtime, not build time. For production, you still need tsc.
❌ No JSX transformation
React JSX still needs Babel or a bundler:
// This WONT work directly in Node.js 24
const element = <div>Hello</div>; // Syntax error!
❌ No CSS modules, asset imports
// These still require a bundler
import styles from ./styles.css;
import image from ./logo.png;
When to Keep Your Bundler
| Use Case | Native TypeScript | Bundler Required |
|---|---|---|
| Backend API servers | ✅ Yes | Optional |
| CLI tools | ✅ Yes | Optional |
| Simple scripts | ✅ Yes | No |
| React/Vue frontend | ❌ No | Yes |
| Full-stack apps | Partial | Yes |
| Library/package publishing | ❌ No | Yes |
Troubleshooting Common Issues
Error: “ERR_UNKNOWN_FILE_EXTENSION”
$ node app.ts
Error [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension: .ts
Fix: Ensure youre using Node.js 24+:
node --version # Must be v24.0.0 or higher
Error: “Cannot find module”
import { helper } from ./utils/helper; // No .js extension
Fix: Add .js extension for ESM compatibility:
import { helper } from ./utils/helper.js;
Types Not Available at Runtime
// type-utils.ts
export type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
// This type information is stripped at runtime
function handleResult<T>(result: Result<T>): T {
if (!result.success) {
throw new Error(result.error); // result.error might not exist!
}
return result.data;
}
Fix: Use type guards for runtime validation:
function isSuccess<T>(result: Result<T>): result is { success: true; data: T } {
return result.success;
}
Mixed .js and .ts Projects
project/
├── src/
│ ├── legacy.js # Still works
│ └── new.ts # Uses native TypeScript
Node.js 24 handles mixed extensions seamlessly—just run:
node --experimental-strip-types src/index.ts
Real-World Example: Migrating an Express API
Heres a complete migration story from traditional TypeScript to native:
Before (package.json – 2025)
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node-dev --respawn src/index.ts"
},
"devDependencies": {
"typescript": "^5.3.0",
"ts-node-dev": "^2.0.0",
"@types/node": "^20.0.0"
}
}
After (package.json – 2026)
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "node --watch --experimental-strip-types src/index.ts"
},
"devDependencies": {
"typescript": "^5.7.0", // Still needed for type-checking
"@types/node": "^22.0.0"
}
}
The Code (src/index.ts)
import express from express;
interface AppConfig {
port: number;
env: string;
}
const config: AppConfig = {
port: parseInt(process.env.PORT || 3000),
env: process.env.NODE_ENV || development
};
const app = express();
app.get(/health, (req, res) => {
res.json({ status: ok, timestamp: new Date().toISOString() });
});
app.get(/api/users, (req, res) => {
const users = [
{ id: 1, name: Alice, role: admin },
{ id: 2, name: Bob, role: developer }
];
res.json(users);
});
app.listen(config.port, () => {
console.log(`Server running in ${config.env} mode on port ${config.port}`);
});
export default app;
Run it:
npm run dev
# Server running in development mode on port 3000
Development is instant. Production builds still work:
npm run build && npm start
# Same result, optimized bundle
FAQ
Q: Is Node.js 24 LTS now?
A: Yes, as of March 2026, Node.js 24 is the recommended LTS version for new projects.
Q: Can I delete my node_modules/.bin/tsc?
A: No! You still need TypeScript for:
- Type checking (
tsc --noEmit) - Declaration file generation
- Production builds with optimization
Q: Does this work with Deno?
A: Deno has had native TypeScript for years. This is Node.js catching up. The key difference is Node.js 24 maintains backward compatibility with the existing npm ecosystem.
Q: What about type definitions (.d.ts)?
A: They still work! Node.js 24 respects @types packages for IntelliSense and type checking.
Q: Can I use this in production?
A: For development: absolutely. For production, its still recommended to use traditional builds with optimization (minification, tree-shaking). Native TypeScript is primarily a development experience improvement.
Q: Does this replace ts-node?
A: Yes, for most use cases. ts-node was a workaround for exactly this feature. You can remove it from your dependencies.
Q: What about Jest and testing?
A: Jest still needs configuration for TypeScript (via ts-jest or swc). Test execution can use native TypeScript, but test configuration still typically requires compilation.
Q: Can I use this with Docker?
A: Yes! Update your Dockerfile:
FROM node:24-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "--experimental-strip-types", "src/index.ts"]
The Bigger Picture: Why This Matters
This isnt just a convenience feature—it signals a shift in how we think about JavaScript tooling:
- Build tools become optimization tools, not required infrastructure
- TypeScript moves from compilation to interpretation
- Development loop tightens from seconds to milliseconds
- Lower barrier to entry for new JavaScript developers
The ecosystem will evolve. Some bundlers will become optimizers. Some will fade away. But for now, enjoy the simplest TypeScript developer experience in the languages history.
Conclusion
Node.js 24s native TypeScript support is a game-changer for development workflows. While it wont replace bundlers for production builds, it dramatically simplifies the development experience:
- No more ts-node or tsx for simple execution
- 65% faster npm installs with npm v11
-
Instant hot reload with
--watch - Zero extra dependencies for TypeScript execution
Your move: Update to Node.js 24, remove ts-node from devDependencies, and enjoy the simpler workflow.
nvm install 24
nvm use 24
node --watch app.ts
Welcome to 2026. The build step is (mostly) dead. Long live TypeScript.
Tags: javascript,typescript,nodejs,programming,tutorial,webdev,devops
