Svelte really is that fast

If you search online, you’ll find countless benchmarks claiming to compare the performance of various JavaScript frameworks with each other. More often than not, the benchmarks are overly simplistic and fail to reflect real-world scenarios. In other cases, they compare apples and oranges, for instance by pitting a fully fledged framework against a lightweight library that cover only a small subset of the framework’s functionality.
Right now, I’m at that point in my career again where I am starting development on a new web application and need to choose the “right” JavaScript framework. This time, I decided to look for academic studies on the performance of JavaScript frameworks and sadly, didn’t find as many as I had hoped. I did come across one particular study that compares Angular, React, Vue, Svelte and Blazor with each other. Its main drawback is that the comparison was done in 2021 – a lifetime ago in tech terms – but I think it’s still worth reading.
Before I dive into the summary, I want to share something I found mildly amusing.
The paper is published in the Journal of Web Engineering, and if you visit its
website, you’ll notice it includes /index.php/
in the URL. I’m not sure why.
Is it a deliberate choice? Or is it a sign of questionable web engineering?
It is estimated that up to 97% of websites today use JavaScript, with more than 80% also relying on a library or framework. JavaScript is often used to manage UI state changes within single page applications, allowing users to interact without reloading the entire page. While this can be done manually via the DOM API, it’s often error prone.
Modern web frameworks wrap the DOM API and provide a custom declarative syntax. This means that application code can simply describe the desired UI state, and the framework automatically generates the necessary DOM API calls to reflect that state in the browser.
When using the DOM API directly, the amount of script execution required to update the UI scales linearly with the complexity of the change. However, when DOM API calls are generated dynamically by a framework, the framework must first determine exactly which updates are necessary, introducing additional overhead. Furthermore, the chosen rendering strategy can greatly affect the number of DOM API calls made. A bad strategy may result in noticeable delays even for small updates, which is why it makes sense to compare the strategies used by major JavaScript frameworks.
The study looks at five popular frameworks: Angular, React, Vue, Svelte, and Blazor. All are JavaScript-based except for Blazor, which uses WebAssembly. Blazor applications are written in C# and run in a .NET runtime compiled to WebAssembly. Because WebAssembly modules lack direct access to the DOM, they rely on an additional JavaScript interoperability layer, which can introduce extra overhead.
Framework | Version |
---|---|
Angular | 11.2.3 |
React | 17.0.1 |
Vue | 3.0.7 |
Svelte | 3.35.0 |
Blazor | 5.0.3 |
All five frameworks follow some variant of the Model-View-ViewModel (MVVM) pattern. In MVVM, developers define components that bind an application’s data sources to views, so that changes in the data are automatically reflected in the UI.
Each framework continuously tries to keep the state of the DOM tree synchronised with the state of the component tree defined in application code. This is done using two distinct methods.
The first method, known as virtual DOM (vDOM)-based rendering, is used in React, Vue, and Blazor. It works by comparing the two trees and calculating the minimum set of changes needed to transform one into the other. This generally has a time complexity of , but can often be simplified to by making assumptions that usually apply in browser applications.
The second method is used by Angular and Svelte. Here, there is no separate step for calculating all required changes to the DOM. Instead, each component directly updates its corresponding section of the DOM by tracking the values of its data bindings.
From a performance standpoint, using a virtual DOM can introduce overhead not present in a binding-based rendering strategy.
All of the reviewed frameworks perform DOM updates within a render loop that walks through the component tree. The cost of this render loop depends on the size of the input and fixed costs.
A render loop involves two types of work: creating new elements and updating existing ones. Creating elements is generally straightforward and costs the same for each framework, regardless of rendering strategy.
Updating existing elements is where the rendering strategy makes a noticeable difference. Angular, for example, always walks through the entire component tree, resulting in a lot of unnecessary work when only a small part of the tree needs updating. React and Blazor walk only through the subtree of the component that initiates the render loop, which is usually more efficient but may still require some unnecessary work for descendants whose output has not changed. Vue and Svelte, on the other hand, process only “dirty” components whose output has changed. This requires the framework to track which components are dirty. Vue does this at runtime, Svelte handles it at compile time.
Finally, component output can be classified as either static or dynamic content, with static content remaining unchanged after the component’s initial render. Frameworks that can optimise for static content may have a performance advantage over those that cannot.
Several benchmarks were conducted using Angular, React, Vue, Svelte and Blazor. The authors found significant differences in performance between the frameworks, especially as input size increased. , outperforming the others across literally every benchmark.
Svelte is the fastest framework when creating static elements, while React is generally among the slowest:
n | Angular (ms) | React (ms) | Vue (ms) | Svelte (ms) | Blazor (ms) |
---|---|---|---|---|---|
100 | 3 | 2 | 1 | 1 | 3 |
500 | 9 | 9 | 3 | 2 | 8 |
1000 | 16 | 11 | 6 | 3 | 13 |
5000 | 85 | 77 | 28 | 14 | 61 |
10000 | 177 | 200 | 47 | 24 | 123 |
25000 | 844 | 956 | 95 | 63 | 371 |
50000 | 2520 | 3559 | 173 | 98 | 964 |
Svelte is also the fastest when creating components arranged as a binary tree. However, in this case, Blazor is the slowest by a considerable margin:
n | Angular (ms) | React (ms) | Vue (ms) | Svelte (ms) | Blazor (ms) |
---|---|---|---|---|---|
128 | 20 | 7 | 16 | 3 | 17 |
512 | 75 | 32 | 53 | 10 | 59 |
1024 | 120 | 55 | 84 | 22 | 128 |
4096 | 216 | 137 | 223 | 83 | 485 |
8192 | 297 | 233 | 313 | 142 | 966 |
16384 | 469 | 394 | 485 | 233 | 1870 |
32768 | 774 | 733 | 897 | 482 | 3644 |
When updating the root component of a tree with components, :
n | Angular (ms) | React (ms) | Vue (ms) | Svelte (ms) | Blazor (ms) |
---|---|---|---|---|---|
128 | 3 | 7 | <1 | <1 | 3 |
512 | 12 | 23 | <1 | <1 | 3 |
1024 | 14 | 42 | <1 | <1 | 2 |
4096 | 32 | 92 | <1 | <1 | 3 |
8192 | 32 | 92 | <1 | <1 | 3 |
16384 | 43 | 211 | <1 | <1 | 2 |
32768 | 103 | 379 | <1 | <1 | 3 |
When updating a leaf component in a component tree of components, Angular is the only framework that lags slightly behind. This is because it performs the same amount of work regardless of what has actually changed:
n | Angular (ms) | React (ms) | Vue (ms) | Svelte (ms) | Blazor (ms) |
---|---|---|---|---|---|
128 | 3 | <1 | <1 | <1 | 1 |
512 | 13 | <1 | <1 | <1 | 1 |
1024 | 14 | 1 | <1 | <1 | 1 |
4096 | 33 | 4 | <1 | <1 | 3 |
8192 | 33 | 3 | <1 | <1 | 5 |
16384 | 44 | 5 | <1 | <1 | 4 |
32768 | 104 | 4 | <1 | <1 | 8 |
Finally, the table below shows the script execution time when updating the entire component tree of components, where each component contains primarily static content:
n | Angular (ms) | React (ms) | Vue (ms) | Svelte (ms) | Blazor (ms) |
---|---|---|---|---|---|
128 | 4 | 34 | 20 | 2 | 28 |
256 | 8 | 44 | 32 | 3 | 60 |
512 | 17 | 66 | 42 | 5 | 101 |
1024 | 27 | 101 | 72 | 10 | 250 |
2048 | 29 | 235 | 91 | 20 | 502 |
4096 | 44 | 289 | 149 | 54 | 1020 |
8192 | 238 | 841 | 311 | 80 | 2013 |
Overall, the results are in line with what would be expected given the characteristics of each rendering strategy.
The WebAssembly-based Blazor shows significantly worse performance than its JavaScript-based competitors. However, from these benchmarks alone it’s impossible to determine whether this is due to Blazor itself or a fundamental limitation of using WebAssembly for this purpose.
Meanwhile, Svelte demonstrates three key characteristics that likely contribute most to improved performance:
- The use of a reactivity system to automatically detect dirty components
- An optimising compiler that generates component update code which ignores static content
- A binding-based rendering approach rather than a virtual DOM
- Svelte’s on fire, yo