How Qwik’s Astro Integration Beats Both React and Vanilla JS
Paul Scanlon tests Qwik with Astro, and finds it's the best of both worlds — lighter than React and less verbose than Vanilla JavaScript.
Nov 23rd, 2023 8:47am by
Benefits of Using Astro
Astro, among other great things, ships zero JavaScript to the client (browser) by default; but, depending on what you’re building, you can choose which pages include JavaScript and which pages don’t. What this means for the end-user experience is that pages will load super fast, because JavaScript is only included on the pages where it’s really needed. However, when you need JavaScript to enable some kind of interactivity, you have two options…or so I thought.Choose Your JavaScript Wisely
If you require any kind of interactivity in your site, you can either:- Write Vanilla JavaScript using inline script elements, which are very lightweight but can become difficult to maintain (not to mention gross to look at, and write); or
- Use a JavaScript framework like React, Svelte or Vue etc., which will add additional bloat to the page but will likely be easier to maintain.
A Simple Sample
To put the theory into practice, I experimented with three approaches to building an interactive sidebar navigation and have prepared a slightly stripped-down version of my site’s sidebar navigation. There’s a branch for each of my experiments available on the following GitHub links.- Vanilla Js – main branch: https://github.com/PaulieScanlon/simple-sidebar-navigation-astro
- React – feat/add-react: https://github.com/PaulieScanlon/simple-sidebar-navigation-astro/tree/feat/add-react
- Qwik – feat/add-qwik: https://github.com/PaulieScanlon/simple-sidebar-navigation-astro/tree/feat/add-qwik
<Layout />, and can be found in src/layouts/layout.astro | .jsxon each of the branches.
1. Vanilla JavaScript
You can see thesrc code for this example here: src/layouts/layout.astro
<script>
var isNavOpen = false;
const menu = document.querySelector('#menu');
const lightbox = document.querySelector('#lightbox');
const sidebar = document.querySelector('#sidebar');
const lightboxOpen = 'fixed';
const lightboxOpenLg = 'lg:hidden';
const lightboxClosed = 'hidden';
const sidebarResponsiveClosed = '-left-[240px]';
const sidebarResponsiveOpen = 'left-[max(0px,calc(50%-45rem))]';
const menuIcon = 'M4 6h16M4 12h16m-7 6h7';
const closeIcon = 'M6 18L18 6M6 6l12 12';
const menuPath = document.querySelector('#menuPath');
menuPath.setAttribute('d', menuIcon);
sidebar.classList.add(sidebarResponsiveClosed);
lightbox.classList.add(lightboxClosed);
const handleNav = () => {
if (isNavOpen) {
menuPath.setAttribute('d', menuIcon);
lightbox.classList.remove(lightboxOpen, lightboxOpenLg);
sidebar.classList.remove(sidebarResponsiveOpen);
lightbox.classList.add(lightboxClosed);
sidebar.classList.add(sidebarResponsiveClosed);
isNavOpen = false;
} else {
menuPath.setAttribute('d', closeIcon);
lightbox.classList.remove(lightboxClosed);
lightbox.classList.add(lightboxOpen, lightboxOpenLg);
sidebar.classList.remove(sidebarResponsiveClosed);
sidebar.classList.add(sidebarResponsiveOpen);
isNavOpen = true;
}
};
menu.addEventListener('click', handleNav);
lightbox.addEventListener('click', handleNav);
</script>
Where Things Can Go Wrong
To give you an example. If I use the lightbox as an example (the lightbox is the semi-transparent black color that is visible when the navigation is open), you should be able to see just how many fiddly little things are involved when using Vanilla JavaScript. From grabbing a reference to theid, creating variables to hold the different class names (I’m using Tailwind by the way), then there’s the classList methods to add and remove styles, plus the eventListener that needs to be attached to the DOM element.
const lightbox = document.querySelector('#lightbox');
const lightboxOpen = 'fixed';
const lightboxOpenLg = 'lg:hidden';
const lightboxClosed = 'hidden';
const handleNav = () => {
...
if (isNavOpen) {
lightbox.classList.remove(lightboxOpen, lightboxOpenLg);
lightbox.classList.add(lightboxClosed);
isNavOpen = false;
} else {
...
lightbox.classList.remove(lightboxClosed);
lightbox.classList.add(lightboxOpen, lightboxOpenLg);
isNavOpen = true;
}
};
lightbox.addEventListener('click', handleNav);
<div
id='lightbox'
aria-label='lightbox'
tab-index='0'
role='button'
class='z-20 top-0 left-0 w-screen h-screen bg-custom-background opacity-80'
>
</div>
id from the HTML element, the navigation would break, making it impossible for people to use my site!
That said, the one thing this approach has got going for it is that it’s super lightweight. Vanilla JavaScript, or JavaScript that the browser can natively understand, doesn’t require any additional runtimes to be downloaded for it to work.
2. React
You can see thesrc code for this example here: src/layouts/layout.jsx
port { useState } from 'react';
const [isNavOpen, setIsNavOpen] = useState(false);
const handleNav = () => {
setIsNavOpen(!isNavOpen);
};
<div
aria-label='lightbox'
tab-index='0'
role='button'
className={`z-20 top-0 left-0 w-screen h-screen bg-custom-background opacity-80 ${isNavOpen ? 'fixed' : 'hidden'} lg:hidden`}
onClick={handleNav}
/>
3. Qwik
You can see thesrc code for this example here: src/layouts/layout.jsx
import { useSignal, $ } from '@builder.io/qwik';
const isNavOpen = useSignal(false);
const handleNav = $(() => {
isNavOpen.value = !isNavOpen.value;
});
<div
aria-label='lightbox'
tab-index='0'
role='button'
className={`z-20 top-0 left-0 w-screen h-screen bg-custom-background opacity-80 ${isNavOpen.value ? 'fixed' : 'hidden' } lg:hidden`}
onClick$={handleNav}
></div>
The Results
Below are the results of the overall page size (which includes HTML and CSS) for each of the methods mentioned above:- Vanilla JS: 9.7kb
- React: 56.8kb
- Qwik: 11.2kb
Qwik, in my opinion, is the best of both worlds — it’s less verbose to write than Vanilla JavaScript and is considerably lighter compared to React.What I also like about the Qwik approach is that the JavaScript is inlined, like it would have been had I written it using the Vanilla method. So there’s no actual .js files downloaded when you look in the Network tab of your browser’s dev tools.
There’s More to Qwik
But that’s not all. Qwik uses a very different approach to handling updates to React. But rather than try to explain it, the Qwik docs have covered it very well here: Resumable vs. Hydration. Another thing I like about this approach is that there are no client directives required. For example, when using React you need to tell Astro that the component (in this case, Layout) is client:only, which means Astro will skip rendering it on the server, and hopefully it’s clear why JavaScript that enables interactivity in the browser doesn’t need to be rendered on the server.
<Layout client:only='react'>
<slot />
</Layout>
<Layout>
<slot />
</Layout>
Final Thoughts
The Qwik Astro integration is currently in the early stages of development. You’ll see from my sample repository that I’m using version 0.1.16; so, as impressed as I am with it, I’m going to hold off implementing it in my site for just a little longer. That said, the folks were super “qwik” in dealing with an issue I opened (thanks again Jack!) so I’m confident a stable v1 will be ready soon, and when it is, I’m 100% ready to implement it on my site. If you have an Astro site and need interactivity, give Qwik a try. The two technologies compliment one another really rather well.
YOUTUBE.COM/THENEWSTACK
Tech moves fast, don't miss an episode. Subscribe to our YouTube
channel to stream all our podcasts, interviews, demos, and more.