javascript
-
Why javascript:void(0) Needs to Stay in the Past
A few months ago, I ran across a
javascript:void(0)in the wild. I’m not going to get into the specific context because it doesn’t really matter, also it gets a bit too personal for this blog post. But I took a screenshot, thought “huh, that’s weird,” and promptly forgot about it.Then I was scrolling back through my screenshots and found it again. So here we are. Let’s talk about
javascript:void(0)and why you shouldn’t be using it in 2026.Here’s the screenshot that started this:

What Does It Actually Do?
If you’ve never encountered this pattern before, here’s the quick version.
voidis a JavaScript operator, so not a function, and its only job is to evaluate the expression next to it, throw away the result, and returnundefined. That’s it. That’s the whole job.When a browser receives
undefinedfrom clicking a link, it does nothing. No navigation, no page refresh. It’s a way to override the browser’s default behavior for an anchor tag.In practice, it looked like this:
<a href="javascript:void(0);" onclick="openModal()">Click Me</a>The
hrefprevents the browser from doing anything, and theonclickfires whatever JavaScript you actually wanted to run. Clever? Sure. A good idea today? No.Why Did We Use It?
Back in the day, we wanted to stop the browser from doing its default thing, following a link, so we could trigger events and make web pages more interactive. This was typically done on anchor tags because, well, that’s what we had. JavaScript didn’t give us a better way to handle it at the time, so
javascript:void(0)became the go-to pattern.It worked. But “it works” and “it’s a good idea” are two very different things.
Three Reasons to Stop Using It
1. It Breaks the Anchor Tag’s Purpose
The biggest issue is that
javascript:void(0)completely overrides what an anchor tag is supposed to do. An<a>tag exists to link to things. When you stuff JavaScript into thehref, you’re hijacking the element’s entire reason for existing.We’ve moved on from needing to do this. If you want something clickable that triggers behavior, use a
<button>. If you want a link that also has JavaScript behavior, give it a real URL as a fallback.2. Separation of Concerns
Modern best practices tell us that HTML should define the structure of the page, and JavaScript should define the behavior. When you’ve got JavaScript living inside an
hrefattribute or relying on inlineonclickhandlers, you’re mixing the two in ways that make code harder to maintain and reason about.The better approach? Use
event.preventDefault()in your JavaScript:<a href="/fallback-page" id="myLink">Click Me</a>document.getElementById('myLink').addEventListener('click', function(event) { event.preventDefault(); openModal(); });This way, if JavaScript is disabled or fails to load, the link still works. There’s a fallback behavior, which matters for accessibility and backwards compatibility. The HTML stays clean, and the behavior lives where it belongs, in your JavaScript files.
Now, I will say that plenty of modern front-end frameworks add their own semantic patterns and play pretty loosey-goosey with this separation of concerns rule. But even React’s
onClickhandlers and Vue’s@clickdirectives are compiled and managed in a way that’s fundamentally different from jamming raw JavaScript into an HTML attribute.3. Content Security Policy Will Block It
I’d like to believe Security still matters in 2026 so lets talk about the Content Security Policy (CSP).
CSP is a set of rules that a web server sends to the browser via HTTP headers, telling the browser what resources the page is allowed to load or execute. Before CSP, browsers just assumed that if code was in the HTML document, it was meant to be there. Web pages were incredibly vulnerable to cross-site scripting (XSS) attacks.
With CSP, the server tells the browser: “Only execute JavaScript if it comes from my own domain. Do not execute any code written directly inside the HTML file.”
A proper CSP header looks something like this:
Content-Security-Policy: default-src 'self'; script-src 'self';This is great for security. But guess what
javascript:void(0)is? Inline JavaScript. A strict CSP will block it.So if you see a site still using
javascript:void(0), check the response headers. Chances are you’ll find something like:Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';See that
'unsafe-inline'addition? That’s the security risk. By addingunsafe-inline, the developer is telling the browser to trust all inline scripts. Every single one. So if an attacker manages to inject JavaScript onto the page, the browser will execute it without hesitation.You’re weakening your entire site’s security posture just to keep a legacy pattern alive. That’s not a tradeoff worth making.
You Probably Don’t Even Need to Think About This
If you’re working with any modern JavaScript framework, this problem is already solved for you.
React, Svelte, Vue, Solid, whatever you’re using, they all ship components that handle default browser behavior the right way. Take forms as an example. The raw HTML
<form>element will, by default, submit and trigger a full page navigation. That’s why developers used to manually callevent.preventDefault()everywhere. But now, frameworks like Next.js, Remix, and SvelteKit give you a<Form>component (or equivalent) that overrides that default behavior for you. No page reload. No manual prevention.The same applies to links, buttons, and pretty much any interactive element. The framework’s component handles the wiring so you don’t have to remember the low-level browser quirks. You import the component, use it, and move on.
That’s the real reason
javascript:void(0)feels so out of place in 2026. It’s not just that we have better patterns available, but the tooling has abstracted the problem away entirely. The history is worth knowing, because understanding why things work the way they do makes you a better developer! -
Svelte 5 Runes: A React Developer's Guide to Reactivity
Continuing my series on Svelte topics, today we’re talking about runes. If you’re coming from React, this is is going to be a different way to work with reactivity in modern JavaScript.
These blog posts might be what is considered the basics, but it helps me learn and think through the topics if I work on blog posts around the important things that every developer needs to know.
What Are Runes?
In Svelte 5, runes are special symbols that start with the dollar sign (
$). They look like regular JavaScript functions, but they’re actually compiler directives, reserved keywords that tell the Svelte compiler how to wire up reactivity during the build step.If you’ve used decorators in Python or macros in other languages, runes fill a similar role. They look like standard JavaScript, but the compiler transforms them into something more powerful behind the scenes.
Let’s walk through the four runes you’ll use most.
$state— The Engine of Reactivity$stateis the foundation. It declares reactive state in your component.<script> let count = $state(0); </script> <button onclick={() => count++}>{count}</button>In React,
useStatereturns an immutable value and a setter function, so you always need thatsetCountcall. In Svelte,$statereturns a deeply reactive proxy. You just mutate the value directly, and the compiler handles the rest. No setter function, no spread operators for nested objects. It just works.$derived— Computed Values Without Dependency ArraysIn React, you’d reach for
useMemohere, and you’d need to explicitly declare a dependency array so React knows when to recalculate.<script> let count = $state(0); let doubled = $derived(count * 2); </script>Dependency arrays are prone to human error. We forget what depends on what, and that leads to stale data or unnecessary recalculations.
$derivedautomatically tracks whatever state variables are used inside of it. No dependency array needed. It just reads what it reads, and recalculates when those values change.$effect— Side Effects That Actually Make SenseThis is the equivalent of
useEffectin React, which is notoriously tricky. Missing dependencies, stale closures, infinite loops… all the big gotchas are in useEffect calls.In Svelte,
$effectis used to synchronize state with external systems, like writing to local storage or updating a canvas:<script> let theme = $state('dark'); $effect(() => { localStorage.setItem('theme', theme); }); </script>Just like
$derived, it automatically tracks its dependencies and only runs when the state it reads actually changes. No dependency array, no cleanup function gotchas. It runs when it needs to run. That’s it.$props— Clean Component InterfacesEvery framework needs a way to pass data into components. In Svelte 5,
$propsmakes this look like standard JavaScript object destructuring:<script> let { name, age, role = 'viewer' } = $props(); </script> <p>{name} ({age}) - {role}</p>Default values, rest parameters, renaming … it all works exactly how you’d expect from CommonJS. If you know destructuring, you already know
$props. It’s readable, predictable, and there’s nothing new to learn.Runes Me Over
You’ve probably noticed a theme. Svelte 5 runes eliminate a whole class of bugs that come from manually managing dependencies. React makes you think about when things should update. Svelte’s goal is to figure it out for you at compile time.
-
Svelte vs React: State Management Without the Ceremony
Continuing my Svelte deep-dive series, let’s talk about state management and reactivity. This is where the differences between React and Svelte can ‘feel’ much different.
React’s State Ceremony
In React, state requires a specific ritual. You declare state with
useState, which gives you a getter and a setter:const [count, setCount] = useState(0); function increment() { setCount(count + 1); }Want to update a variable? You have to call a function. You can’t just reassign
count. React won’t know anything changed. This is fine once you internalize it, but it adds ceremony to what should be a simple operation.Then there’s
useEffect, which is where things get tricky. You need to understand dependency arrays, and if you get them wrong, you’re looking at infinite loops or stale data:useEffect(() => { document.title = `Count: ${count}`; }, [count]); // forget this array and enjoy your infinite loopSome of
useEffectusage is actually unnecessary and likely using it wrong. If you’re using it for data transformations, derived values from state or props, or responding to user events, you’re probably reaching for the wrong tool.The React docs themselves will tell you that you might not need an effect. It’s a common source of bugs and confusion, especially for developers who are still building their mental model of React’s render cycle.
Svelte: Reactivity Through the Language Itself
Svelte takes a fundamentally different approach. Reactivity is baked into the language semantics. Want to declare state? Just declare a variable:
<script> let count = $state(0); function increment() { count += 1; } </script> <button onclick={increment}>{count}</button>That’s it. You assign a new value, and the DOM updates. The Svelte compiler sees your assignments and automatically generates the code to update exactly the parts of the DOM that depend on that variable. No virtual DOM diffing, no setter functions, no dependency arrays to manage.
Need a derived value? Svelte has you covered with
$derived:<script> let count = $state(0); let doubled = $derived(count * 2); </script> <p>{count} doubled is {doubled}</p>In React, you’d either compute this inline, use
useMemowith a dependency array, or… if you didn’t know better reach foruseEffectand a second piece of state (please don’t do this).Svelte’s
$effectrune exists for side effects like updatingdocument.titleor logging, but you should reach for it far less often thanuseEffectin React. The compiler handles most of whatuseEffectgets used for automatically.More Svelte comparisons coming as I keep digging in. Thanks for Svelting with me.
-
Svelte vs React: The Virtual DOM Tax You Might Not Need
I’m diving more into Svelte and SvelteKit lately, and I’m going to be writing a few posts about it as I learn. Fair warning: some of these will be general knowledge posts, but writing things out helps me internalize the details.
The Virtual DOM Question
It’s well known that React relies on a virtual DOM. The basic idea is that React maintains a copy of the DOM in memory, diffs it against the actual DOM, and then batches the changes to update the real thing. This works, but having to maintain this virtual DOM can lead to complications and confusion around what triggers a render or a re-render. If you’ve ever stared at a
useEffectdependency array wondering why your component is re-rendering, you know what I mean.Svelte takes a completely different approach. It’s not a library you ship to the browser, it’s a compiler step. You write Svelte code, and it compiles down to highly optimized vanilla JavaScript that surgically updates the DOM directly. No virtual DOM to maintain. No diffing algorithm running in the background. The framework essentially disappears at build time, and what you’re left with is just… JavaScript.
Templating That Feels Like the Web
I like how Svelte handles the relationship between HTML, CSS, and JavaScript. React forces you to write HTML inside JavaScript using JSX. You get used to it, sure, but it’s a specific way of thinking about your UI that can take some getting used to.
Svelte flips this around. Your
.sveltefiles are structured more like traditional web pages — you’ve got<script>tags for your JavaScript, regular HTML for markup, and<style>tags for CSS. Everything lives in one file, but there’s a clear separation between the three concerns.If you’ve ever worked with Django templates, Laravel Blade, or Ruby on Rails views, this will feel immediately familiar. It’s a lot closer to how the web actually works than JSX’s “everything is JavaScript” approach. For someone coming from those backgrounds, the learning curve is noticeably gentler.
More Svelte posts coming as I dig deeper. That’s all for now!
-
Introducing EmDash — the spiritual successor to WordPress that solves plugin security
Today we are launching the beta of EmDash, a full-stack serverless JavaScript CMS built on Astro 6.0. It combines the features of a traditional CMS with modern security, running plugins in sandboxed Worker isolates.
-
Turborepo is a build system optimized for JavaScript and TypeScript, written in Rust.
-
From SUnit to Vitest: A Brief History of JavaScript Testing
I care a lot about testing. I don’t know if that’s obvious yet, but hopefully it’s obvious. I wanted to trace the lineage of the testing tools we use today, because I think understanding where they came from helps you appreciate why things work the way they do.
Where It All Started
Automated testing as we know it really started with SUnit for the Smalltalk language. Kent Beck created it back in 1989, and it established the patterns that every test framework still borrows from today.
In 1997, Kent Beck and Erich Gamma ported those ideas to Java and created JUnit. JUnit was, or is, incredibly influential to pretty much every unit testing framework you’ve ever used. The test runner, assertions, setup and teardown, all of that traces back to JUnit.
But I’m going to focus on the JavaScript side of things here.
Jest: The Facebook Era
Jest was originally created at Facebook in 2011 as part of a major platform rewrite. It became the dominant testing framework for React and Node.js codebases, and in 2022, Facebook released it to the OpenJS Foundation.
Jest works well, but it carries some baggage. It requires a transpilation pipeline, something that was common a decade ago but feels burdensome now. If you want to use ESM modules, there’s an extra step involved. It’s adds friction.
So what else is there?
Vitest: The Modern Alternative
Vitest is a modern alternative, built on top of Vite. It supports ESM modules and TypeScript out of the box; so no transpilation step needed. And because Vite has HMR (hot module replacement), the watch mode for rerunning tests is very fast.
Vitest was initially created by Anthony Fu and the company behind Vue.js. The initial commit was in December 2021, so it’s a relatively recent project. They’ve made incredible progress since then.
Vitest uses Vite under the hood. So lets look at that briefly.
Why Vite Is So Fast
Vite’s speed comes from esbuild, which is written in Go. It compiles directly to native machine code, so it bypasses the JavaScript engine’s overhead entirely. It can transform TypeScript significantly faster because it doesn’t need to go through the JS engine. And because it’s Go, it’s multithreaded.
But things are changing. In Vite 8, the bundler is moving from esbuild to Rolldown. This is new tool written in Rust that combines the best of esbuild and Rollup.
Why? Currently, Vite uses esbuild during development but switches to Rollup for production builds. Two different tools for two different use cases. Rolldown unifies both into a single tool that handles dev and production.
What Did We Learn?
Hopefully something! How about a mini review to nail it home:
- 1989: SUnit (Smalltalk) — Kent Beck starts it all
- 1997: JUnit (Java) — the template everything else follows
- 2011: Jest — Facebook’s testing framework, now under OpenJS Foundation
- 2021: Vitest — modern, fast, ESM-native testing built on Vite
- Coming soon: Rolldown replaces esbuild + Rollup in Vite 8
That’s all for now!
-
Garbage Collection: How Python, JavaScript, and Go Clean Up After Themselves
It’s Garbage day for me IRL and I wanted to learn more about garbage collection in programming. So guess what? Now you get to learn more about it too.
We’re going to focus on three languages I work with mostly, Python, JavaScript, and Go. We will skip the rest for now.
Python: Reference Counting
Python’s approach is the most straightforward of the three. Every object keeps track of how many things are pointing to it. When that count drops to zero, the object gets cleaned up immediately. Simple.
There’s a catch, though. If two objects reference each other but nothing else references either of them, the count never hits zero. That’s a reference cycle, and Python handles it with a secondary cycle detector that periodically scans for these orphaned clusters. But for the vast majority of objects, reference counting does the job without any fancy algorithms.
JavaScript (V8): Generational Garbage Collection
Most JavaScript you encounter is running on V8… so Chrome based browsers, Node and Deno. V8 uses a generational strategy based on a simple observation: most objects die young.
V8 splits memory into two main areas:
- The Nursery (Young Generation): New objects land here. This space is split into two halves and uses a scavenger algorithm. It’s fast because it only deals with short-lived variables — and most variables are short-lived.
- Old Space (Old Generation): No, not the deodorant company. Objects that survive a couple of scavenger rounds get promoted here. Old space uses a mark-and-sweep algorithm, which is slower but handles long-lived objects more efficiently.
First it asks, “How long has this object been around?” New stuff gets the quick treatment, and anything that sticks around gets put out to the farm, to be delt where time is less of a premium. It’s a smart tradeoff between speed and memory efficiency.
Go: Tricolor Mark-and-Sweep
Go’s garbage collector also uses mark-and-sweep, but with a twist called tricolor marking. Here’s how it works:
- White objects: These might be garbage but haven’t been checked yet. Everything starts as white.
- Gray objects: The collector has reached these, but it hasn’t scanned their children (the things they reference) yet.
- Black objects: These are confirmed alive — the collector has scanned them and all their references.
The collector starts from known root objects, marks them gray, then works through the gray set — scanning each object’s references and marking them gray too, while the scanned object itself turns black. When there are no more gray objects, anything still white is unreachable and gets cleaned up.
Go’s approach is notable because it runs concurrently. This helps with latency while the GC is running.
Garbage, Collected
Each approach reflects the language’s priorities:
- Python optimizes for simplicity and predictability. Objects get cleaned up correctly when they’re no longer needed
- JavaScript optimizes for speed in interactive applications. Quick cleanup for short-lived objects, thorough cleanup for the rest
- Go optimizes for low latency. Concurrent collection is great for server side processes
References
- Design of CPython’s Garbage Collector — Python Developer’s Guide deep dive into reference counting and cycle detection
- Orinoco: Young Generation Garbage Collection — V8’s parallel scavenger and generational GC design
- A Guide to the Go Garbage Collector — Official Go documentation on the concurrent tricolor collector
-
I compared npm, Yarn, pnpm, and Bun. TLDR version: pnpm wins for most teams, Bun wins if you’re already on the runtime.
Has anyone switched their whole team to Bun yet? How’d that go?
-
I wrote about securing node_modules. Socket, Snyk, Dependabot — each catches different things. Hopefully answering when to use AI to rewrite simple deps you barely use.
Anyone want to build that CLI?