JavaScript in 2026: The Stuff That Actually Matters

In 2026, the JavaScript that matters is native: ES modules, built in fetch with AbortSignal.timeout, optional chaining, and top level await. Skip Babel for features browsers already support, drop axios on small projects, and set a modern build target. Less tooling, smaller bundles, faster pages.
Part of the complete web development guide.
On this page
- Stop shipping polyfills you don’t need
- Modules, not globals
- Async patterns worth knowing in 2026
- Promise.allSettled for anything that can partially fail
- Top-level await in modules
- The DOM APIs you should be using
- Tooling: pick less of it
- Performance habits that pay off
- What to actually take away
- FAQ
- Do I still need a bundler in 2026?
- Is TypeScript worth it for small projects?
- What’s the single biggest JavaScript mistake you see?
Most “JavaScript tips” posts age about as well as milk. The language moves, the tooling moves faster, and half the advice from three years ago is now just noise. Here’s what I actually reach for in 2026, and what I’ve quietly stopped doing.
Stop shipping polyfills you don’t need
The biggest change most teams haven’t caught up with is that the baseline moved. If you’re still running every file through Babel to convert optional chaining, nullish coalescing, or async/await, you’re adding build time and bundle weight for browsers that have supported those features for years. Modern targets handle it natively. Build for the browsers you actually support, not the ones you imagine someone might be using on a decade-old tablet.
Same story with fetch. The days of bundling axios into a tiny project just to make a GET request are mostly over unless you need interceptors or upload progress.
const res = await fetch("/api/orders", {
headers: { "Content-Type": "application/json" },
signal: AbortSignal.timeout(8000),
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const orders = await res.json();
That AbortSignal.timeout() call is the part people miss. A fetch with no timeout can hang forever, and on a busy production site that’s how you end up with a pile of stuck requests and a slow page.
Modules, not globals
If you’re still loading scripts with <script src="app.js"> and hoping load order works out, stop. Native ES modules have been the default for years and they’re simpler than the bundler-era workarounds.
<script type="module" src="/js/main.js"></script>
Inside, you import what you need and nothing leaks to window. This matters more than people think. Global scope pollution is the source of a huge share of weird “why did this break when I added that other script” bugs. Modules scope everything by default. Use them.
Async patterns worth knowing in 2026
Promises and async/await aren’t new, but there are a couple of refinements that make real code cleaner.
Promise.allSettled for anything that can partially fail
Promise.all rejects the whole batch if one item fails. That’s usually not what you want when you’re loading five independent widgets.
const results = await Promise.allSettled([
loadUser(),
loadNotifications(),
loadCart(),
]);
for (const r of results) {
if (r.status === "fulfilled") render(r.value);
else console.warn("Skipped:", r.reason);
}
Top-level await in modules
In an ES module you can await at the top level. No wrapping IIFE, no awkward init function.
const config = await fetch("/config.json").then(r => r.json());
export default config;
Good for config loads and one-time setup. Don’t abuse it for things that should be lazy. If it blocks the module from finishing, it blocks everyone importing it.
The DOM APIs you should be using
Querying and manipulating the DOM is where a lot of old habits stick around. A few that are worth adopting:
querySelectorAllreturns a static NodeList you canforEachdirectly. No moreArray.fromdance.element.closest()for event delegation instead of walkingparentNodein a loop.classList.toggle(name, force)where the second argument is a boolean. Cleaner than branching.IntersectionObserverfor lazy loading and scroll effects instead of scroll event listeners that fire hundreds of times a second.
const io = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
io.unobserve(entry.target);
}
}
}, { rootMargin: "200px" });
document.querySelectorAll("img[data-src]").forEach(img => io.observe(img));
Tooling: pick less of it
The trend I’m happiest about is that the front-end toolchain has been consolidating. For new projects I reach for Vite by default. It’s fast, the config is small, and it handles both dev and production builds without a pile of plugins. TypeScript is worth it on anything that will outlive the week. Run it in strict mode or you’re getting maybe 30% of the benefit.
What I’ve dropped: heavyweight state managers for small apps (native signals and simple stores cover most cases now), CSS-in-JS for projects that don’t need dynamic theming, and jQuery for anything greenfield. jQuery still works fine, but new code doesn’t need it, and pulling it in just because it’s familiar is how codebases accumulate weight.
Performance habits that pay off
A few things I check on every site I touch, whether it’s a personal project or custom web development for a client here on Long Island:
- Ship less JavaScript. Every kilobyte has to be parsed and executed. If a feature can be HTML and CSS, it should be.
- Defer non-critical scripts. Use
type="module"(which defers by default) ordefer. Avoid blocking<script>tags in the head. - Measure before optimizing. Chrome DevTools performance panel and Lighthouse are free. Guessing at bottlenecks wastes time.
- Watch your event listeners. Every
addEventListeneryou add on a long-lived page is memory you have to give back. UseAbortControllerto clean up groups of them.
const controller = new AbortController();
window.addEventListener("resize", onResize, { signal: controller.signal });
window.addEventListener("scroll", onScroll, { signal: controller.signal });
// Later, when the component unmounts:
controller.abort();
What to actually take away
JavaScript hasn’t gotten harder. It’s gotten broader, and the noise around it has gotten louder. The language itself is more capable and more predictable than it was five years ago. If you write modern modules, use native async correctly, lean on the DOM APIs that exist, and keep your toolchain small, you’ll spend your time on the product instead of the plumbing. That’s the whole game.
FAQ
Do I still need a bundler in 2026?
For most sites, yes, but a lighter one. Vite or esbuild handles dev and production fine. You don’t need a dozen plugins unless you have a specific reason.
Is TypeScript worth it for small projects?
If the project will still exist in three months, yes. The type errors you catch at build time are cheaper than the runtime bugs you’d debug later. For a one-off script, plain JS is fine.
What’s the single biggest JavaScript mistake you see?
Sending too much of it. Most performance problems I find aren’t bugs. They’re pages loading megabytes of JavaScript for features that could have been a few lines of CSS.


