Skip to main content

19.5 Pro Tips

This chapter covers advanced patterns and best practices for working with Qwik in production. We'll fully master the $ sign rules, performance optimization, deployment strategies, and the Qwik ecosystem — and celebrate completing the JavaScript curriculum.


Mastering $ Sign Placement Rules​

Rules Summary Table​

Location                        Allowed  Reason
────────────────────────────────────────────────────────
Module top level ✓ chunk can be split per file
component$ top level ✓ top level of component render function
Inside a conditional ✗ chunk location determined at runtime
Inside a loop ✗ dynamic chunk count, cannot optimize
Inside a nested function ✗ cannot serialize from nested scope

Correct Pattern Examples​

import { component$, useSignal, $, useTask$ } from '@builder.io/qwik';

// ✓ $ function at module top level
export const sharedHandler = $(() => {
console.log('shared handler — split into a chunk');
});

// ✓ inside a component at the top level
export const GoodComponent = component$(() => {
const count = useSignal(0);
const visible = useSignal(true);

// ✓ at the component render function top level
const increment = $(() => count.value++);
const toggle = $(() => visible.value = !visible.value);

// ✓ useTask$ at the top level only
useTask$(({ track }) => {
track(() => count.value);
console.log('count changed:', count.value);
});

// ✗ the following is not allowed
// if (visible.value) {
// const badHandler = $(() => {}); // cannot use $ inside a conditional
// }

return (
<div>
<button onClick$={increment}>{count.value}</button>
{/* ✓ inline $ in JSX is always rendered, so this is fine */}
{visible.value && (
<button onClick$={() => count.value--}>Decrement</button>
)}
</div>
);
});

$ Sign Types and Their Roles​

// 1. component$: component definition (largest chunk unit)
export const MyComp = component$(() => { ... });

// 2. $ on event handlers: event chunks
<button onClick$={() => { ... }} />
<input onInput$={(e) => { ... }} />
<form onSubmit$={() => { ... }} />

// 3. useTask$: effect chunk
useTask$(({ track }) => { ... });

// 4. useVisibleTask$: client effect chunk
useVisibleTask$(() => { ... });

// 5. useComputed$: computed chunk
const result = useComputed$(() => { ... });

// 6. $(): split any function into a chunk
const myFn = $(() => { ... });

// 7. routeLoader$, routeAction$, server$: server chunks
export const useData = routeLoader$(async () => { ... });
const fetchData = server$(async () => { ... });

Closure Serialization Issues​

Problem: Capturing External Variables​

// problematic scenario
export const BadExample = component$(() => {
let counter = 0; // plain variable (not serializable)

// this closure captures counter, but
// counter cannot be serialized, so it cannot be resumed
return (
<button onClick$={() => {
counter++; // ✗ capturing a plain variable — value lost after resume
console.log(counter);
}}>
Click
</button>
);
});
// solution: use Signal/Store
export const GoodExample = component$(() => {
const counter = useSignal(0); // Signal (serializable)

return (
<button onClick$={() => {
counter.value++; // ✓ mutate Signal directly
console.log(counter.value);
}}>
Click
</button>
);
});

Handling Non-Serializable External Values​

import { component$, useSignal, useVisibleTask$, noSerialize } from '@builder.io/qwik';
import type { NoSerialize } from '@builder.io/qwik';

interface State {
// type marked with noSerialize
socket: NoSerialize<WebSocket> | undefined;
messages: string[];
}

export const WebSocketDemo = component$(() => {
const state = useStore<State>({
socket: undefined,
messages: [],
});

// WebSocket cannot be serialized → use noSerialize + useVisibleTask$
useVisibleTask$(({ cleanup }) => {
const ws = new WebSocket('wss://echo.websocket.org');

ws.onmessage = (event) => {
state.messages.push(event.data);
};

// store the WebSocket instance with noSerialize
state.socket = noSerialize(ws);

cleanup(() => ws.close());
});

return (
<div>
<button onClick$={() => {
state.socket?.send('Hello!');
}}>
Send message
</button>
<ul>
{state.messages.map((msg, i) => (
<li key={i}>{msg}</li>
))}
</ul>
</div>
);
});

QRL Closure Capture Rules​

export const ClosureRules = component$(() => {
// ✓ useSignal/useStore values are automatically captured
const count = useSignal(0);
const user = useStore({ name: 'Alice' });

// ✓ constant primitive values can be captured
const MAX = 100;

// ✗ storing function results in plain variables is not serializable
// const today = new Date(); // Date object — use with care

// ✓ store in serializable form in a Signal
const todayStr = useSignal(new Date().toISOString());

return (
<button onClick$={() => {
// count: Signal → serialized ✓
// user: Store → serialized ✓
// MAX: primitive → serialized ✓
if (count.value < MAX) {
count.value++;
console.log(user.name, todayStr.value);
}
}}>
{count.value}/{MAX}
</button>
);
});

Islands Architecture​

Qwik's Approach​

Islands architecture is a model where interactive component "islands" float in a "sea" of static HTML.

Traditional islands (Astro, Marko):
Static HTML ──────────────────────────────────────
[Island A] [Island B]
React component Vue component
requires hydration requires hydration

Qwik's approach:
Static HTML + Qwik serialization ──────────────────────────
The entire page is an island! Each event handler is its own chunk
button click chunk input handler chunk form submit chunk
(no hydration, loaded on demand)

Comparing Island Frameworks​

Framework    Island unit         Hydration              Language
────────────────────────────────────────────────────────
Astro component partial hydration any (React/Vue/Svelte)
Marko component streaming hydration Marko
Fresh (Deno) component traditional hydration Preact
Qwik event handler none (resumable) TypeScript/JSX

Intentional Island Pattern in Qwik​

// isolate heavy interactive components in Qwik
export const HeavyChart = component$(() => {
// JS for this component is not loaded until the user clicks
return <canvas id="chart" />;
});

export const StaticPage = component$(() => {
return (
<main>
{/* static content: no JS needed */}
<h1>Blog Post Title</h1>
<p>Long static text content...</p>

{/* interactive island: JS only loads on click */}
<HeavyChart />

{/* another island */}
<CommentSection />
</main>
);
});

Image Optimization​

Using @unpic/qwik​

npm install @unpic/qwik
import { component$ } from '@builder.io/qwik';
import { Image } from '@unpic/qwik';

export const OptimizedImage = component$(() => {
return (
<div>
{/* automatic size optimization, WebP conversion, lazy loading */}
<Image
src="https://example.com/photo.jpg"
layout="constrained"
width={800}
height={600}
alt="Optimized image"
priority={false} // use true for critical images
/>

{/* responsive image */}
<Image
src="/hero.jpg"
layout="fullWidth"
aspectRatio={16 / 9}
alt="Hero image"
priority={true} // use priority for LCP images
/>
</div>
);
});

Manual Image Optimization Pattern​

// vite.config.ts — add image optimization plugin
import { qwikVite } from '@builder.io/qwik/optimizer';

export default defineConfig(() => ({
plugins: [
qwikCity(),
qwikVite({
// image optimization options (experimental)
}),
],
}));
// manual lazy image pattern
export const LazyImage = component$<{
src: string;
alt: string;
width: number;
height: number;
}>(({ src, alt, width, height }) => {
const isLoaded = useSignal(false);

return (
<div
style={{ width, height }}
class={`image-wrapper ${isLoaded.value ? 'loaded' : 'loading'}`}
>
<img
src={src}
alt={alt}
width={width}
height={height}
loading="lazy"
decoding="async"
onLoad$={() => isLoaded.value = true}
style={{ opacity: isLoaded.value ? 1 : 0, transition: 'opacity 0.3s' }}
/>
</div>
);
});

Performance Measurement​

Analyzing a Qwik App with Lighthouse​

# measure with Lighthouse after building
npm run build
npm run preview

# Chrome DevTools → Lighthouse tab — check Performance scores:
# - FCP (First Contentful Paint): target < 1.8s
# - LCP (Largest Contentful Paint): target < 2.5s
# - TBT (Total Blocking Time): should be near 0 with Qwik
# - CLS (Cumulative Layout Shift): target < 0.1
# - TTI (Time to Interactive): should be nearly equal to FCP with Qwik

Bundle Analysis​

# install rollup-plugin-visualizer
npm install -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig(() => ({
plugins: [
qwikCity(),
qwikVite(),
process.env.ANALYZE && visualizer({
open: true,
gzipSize: true,
brotliSize: true,
filename: 'dist/stats.html',
}),
],
}));
# run bundle analysis
ANALYZE=true npm run build
# open dist/stats.html in the browser

Runtime Performance Measurement​

// measure component render performance
export const PerfMonitor = component$(() => {
useVisibleTask$(() => {
// use the Performance API
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime.toFixed(2), 'ms');
}
if (entry.entryType === 'first-input') {
console.log('FID:', (entry as any).processingStart - entry.startTime, 'ms');
}
}
});

observer.observe({ type: 'largest-contentful-paint', buffered: true });
observer.observe({ type: 'first-input', buffered: true });

return () => observer.disconnect();
});

return null;
});

// using the web-vitals library
import { onCLS, onFID, onFCP, onLCP, onTTFB } from 'web-vitals';

useVisibleTask$(() => {
onCLS(({ value }) => console.log('CLS:', value));
onFCP(({ value }) => console.log('FCP:', value, 'ms'));
onLCP(({ value }) => console.log('LCP:', value, 'ms'));
onTTFB(({ value }) => console.log('TTFB:', value, 'ms'));
});

Deployment Strategies​

Deploying to Vercel​

# add Vercel Edge Functions adapter
npm run qwik add vercel-edge

# deploy with Vercel CLI
npm install -g vercel
vercel --prod
// vite.config.ts (after applying the Vercel adapter)
import { vercelEdgeAdapter } from '@builder.io/qwik-city/adapters/vercel-edge/vite';

export default defineConfig(() => ({
plugins: [
qwikCity(),
qwikVite(),
vercelEdgeAdapter(),
],
}));

Deploying to Cloudflare Pages​

# add Cloudflare Pages adapter
npm run qwik add cloudflare-pages

# deploy with Wrangler
npm install -g wrangler
wrangler pages deploy dist/client
// wrangler.toml
[site]
bucket = "./dist/client"

[env.production]
name = "my-qwik-app"

Deploying to Cloudflare Workers​

// vite.config.ts
import { cloudflarePagesAdapter } from '@builder.io/qwik-city/adapters/cloudflare-pages/vite';

export default defineConfig(() => ({
plugins: [
qwikCity(),
qwikVite(),
cloudflarePagesAdapter({
// Cloudflare Workers configuration
workerUrl: './src/workers/worker.ts',
}),
],
}));

Deploying to Node.js (Express)​

npm run qwik add express
npm run build
node dist/server/entry.express.js
// src/entry.express.ts (auto-generated after adding adapter)
import { createQwikCity } from '@builder.io/qwik-city/middleware/express';
import express from 'express';
import { manifest } from '@qwik-client-manifest';
import render from './entry.ssr';

const app = express();
const { router, notFound } = createQwikCity({ render, manifest });

app.use(express.static('dist/client'));
app.use(router);
app.use(notFound);

app.listen(3000, () => console.log('http://localhost:3000'));

Docker Deployment​

# Dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./

EXPOSE 3000
ENV PORT=3000

CMD ["node", "dist/server/entry.express.js"]
# docker-compose.yml
version: '3.8'
services:
qwik-app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- API_KEY=${API_KEY}
restart: unless-stopped

TypeScript Tips​

The QwikJSX Type System​

import type {
QwikIntrinsicElements, // all HTML element types (Qwik version)
QwikHTMLAttributes, // common HTML attributes
QwikMouseEvent, // Qwik mouse event
QwikKeyboardEvent, // Qwik keyboard event
JSXChildren, // children type
} from '@builder.io/qwik';

// extending custom HTML attributes
declare module '@builder.io/qwik' {
interface QwikIntrinsicElements {
'custom-element': QwikHTMLAttributes<HTMLElement> & {
'data-custom': string;
};
}
}

Signal Type Safety​

import type { Signal, ReadonlySignal } from '@builder.io/qwik';

// read-only Signal (when passed as a prop)
interface DisplayProps {
value: ReadonlySignal<number>; // child cannot change the value
}

// read/write Signal (two-way binding)
interface InputProps {
model: Signal<string>; // child can change the value
}

// component type definition
type ButtonProps = {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: Signal<boolean> | boolean; // accepts both Signal and plain value
onClick$?: PropFunction<(event: QwikMouseEvent) => void>;
};

component$ Type Patterns​

import { component$ } from '@builder.io/qwik';
import type { Component, PropFunction } from '@builder.io/qwik';

// generic component
interface ListProps<T> {
items: T[];
renderItem$: PropFunction<(item: T, index: number) => JSXNode>;
keyExtractor: (item: T) => string | number;
}

// note: component$ does not directly support generic type parameters
// use type assertion to handle this
export const GenericList = component$(<T,>({
items,
renderItem$,
keyExtractor,
}: ListProps<T>) => {
return (
<ul>
{items.map((item, i) => (
<li key={keyExtractor(item)}>
{/* cannot call functions directly in JSX — extract to a component */}
</li>
))}
</ul>
);
}) as unknown as Component<ListProps<any>>;

routeLoader$ Type Inference​

// return type is automatically inferred
export const useUser = routeLoader$(async ({ params }) => {
return {
id: params.id,
name: 'Alice',
email: 'alice@example.com',
};
});

// types are automatically inferred at usage
export default component$(() => {
const user = useUser();
// user.value type: { id: string; name: string; email: string; }
console.log(user.value.name); // ✓ type-safe
console.log(user.value.phone); // ✗ type error
});

Migrating from Other Frameworks​

From React to Qwik​

// React component
function Counter({ initialCount = 0 }: { initialCount?: number }) {
const [count, setCount] = useState(initialCount);
const [doubled, setDoubled] = useState(initialCount * 2);

useEffect(() => {
setDoubled(count * 2);
}, [count]);

return (
<div>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<p>Doubled: {doubled}</p>
</div>
);
}

// Qwik equivalent
const Counter = component$<{ initialCount?: number }>(({ initialCount = 0 }) => {
const count = useSignal(initialCount);
const doubled = useComputed$(() => count.value * 2);

return (
<div>
<button onClick$={() => count.value++}>{count.value}</button>
<p>Doubled: {doubled.value}</p>
</div>
);
});

Migration Cheat Sheet​

React → Qwik conversion table
──────────────────────────────────────────────────────────
useState(val) → useSignal(val)
useState({...}) → useStore({...})
useReducer → useStore + $ functions
useEffect(() => {}) → useTask$(({ track }) => {})
useEffect([dep]) → useTask$(({ track }) => { track(() => dep); })
useMemo(() => val, [dep]) → useComputed$(() => val)
useCallback(() => fn) → $ (useCallback is unnecessary)
useRef → useSignal<Element>()
React.memo → automatic in Qwik (no component re-execution)
useContext/createContext → createContextId/useContext/useContextProvider
useLayoutEffect → useVisibleTask$({ strategy: 'document-ready' })

The Qwik Ecosystem​

qwik-ui (Component Library)​

npm install @qwik-ui/headless
# or styled version
npm install @qwik-ui/styled
// using Qwik UI components
import { component$ } from '@builder.io/qwik';
import { Modal, ModalContent, ModalHeader, ModalTrigger } from '@qwik-ui/headless';
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@qwik-ui/headless';

export const UIExample = component$(() => {
return (
<div>
{/* Modal */}
<Modal>
<ModalTrigger>
<button>Open modal</button>
</ModalTrigger>
<ModalContent>
<ModalHeader>Title</ModalHeader>
<p>Modal content</p>
</ModalContent>
</Modal>

{/* Accordion */}
<Accordion>
<AccordionItem value="item1">
<AccordionTrigger>Question 1</AccordionTrigger>
<AccordionContent>Answer 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item2">
<AccordionTrigger>Question 2</AccordionTrigger>
<AccordionContent>Answer 2</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
});

qwik-speak (i18n Internationalization)​

npm install qwik-speak
// src/speak-config.ts
import type { SpeakConfig } from 'qwik-speak';

export const config: SpeakConfig = {
defaultLocale: { lang: 'en', currency: 'USD', timeZone: 'America/New_York' },
supportedLocales: [
{ lang: 'en', currency: 'USD', timeZone: 'America/New_York' },
{ lang: 'ko', currency: 'KRW', timeZone: 'Asia/Seoul' },
{ lang: 'ja', currency: 'JPY', timeZone: 'Asia/Tokyo' },
],
assets: ['app', 'common'],
runtimeAssets: ['runtime'],
};

// public/i18n/en/app.json
{
"app": {
"title": "My App",
"welcome": "Hello, {{name}}!",
"itemCount": "{{count}} items"
}
}

// usage in a component
import { component$ } from '@builder.io/qwik';
import { useTranslate, useSpeakContext } from 'qwik-speak';

export const I18nExample = component$(() => {
const t = useTranslate();

return (
<div>
<h1>{t('app.title')}</h1>
<p>{t('app.welcome', { name: 'Alice' })}</p>
<p>{t('app.itemCount', { count: 5 }, 5)}</p>
</div>
);
});

Partytown Integration (Third-Party Script Optimization)​

// src/root.tsx
import { component$ } from '@builder.io/qwik';
import { QwikPartytown } from '@builder.io/qwik-city';

export default component$(() => {
return (
<html>
<head>
{/* run Google Analytics in a Web Worker */}
<QwikPartytown forward={['dataLayer.push']} />
<script
async
type="text/partytown"
src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
/>
</head>
<body>
<RouterOutlet />
</body>
</html>
);
});

Key Community Libraries​

Package                     Description
─────────────────────────────────────────────────────────
@qwik-ui/headless Headless UI components
@qwik-ui/styled Styled UI components (Tailwind)
qwik-speak i18n internationalization
@unpic/qwik Image optimization
qwik-pwa PWA support (service worker)
@qwikdev/astro Astro + Qwik integration
qwik-jwt JWT utility
qwik-supabase Supabase integration
qwik-firebase Firebase integration

Practical Project: E-Commerce Product Page​

// src/routes/shop/[productId]/index.tsx
import { component$, useSignal, useStore } from '@builder.io/qwik';
import {
routeLoader$,
routeAction$,
Form,
Link,
zod$,
z
} from '@builder.io/qwik-city';
import type { DocumentHead } from '@builder.io/qwik-city';

// server-side data loading
export const useProduct = routeLoader$(async ({ params, error }) => {
const product = await fetchProduct(params.productId);
if (!product) throw error(404, 'Product not found');
return product;
});

export const useRelatedProducts = routeLoader$(async ({ params }) => {
const product = await fetchProduct(params.productId);
return fetchRelatedProducts(product.categoryId, { limit: 4 });
});

// add to cart action
export const useAddToCart = routeAction$(
async (data, { cookie }) => {
const cartId = cookie.get('cart_id')?.value || createCartId();
await addToCart(cartId, data);
cookie.set('cart_id', cartId, { path: '/', maxAge: 7 * 24 * 60 * 60 });
return { success: true };
},
zod$({
productId: z.string(),
quantity: z.number().min(1).max(99),
variant: z.string().optional(),
})
);

export default component$(() => {
const product = useProduct();
const related = useRelatedProducts();
const addToCart = useAddToCart();

const selectedImage = useSignal(0);
const quantity = useSignal(1);
const selectedVariant = useSignal(product.value.variants?.[0]?.id || '');

return (
<div class="product-page">
{/* image gallery */}
<div class="gallery">
<img
src={product.value.images[selectedImage.value]}
alt={product.value.name}
width={600}
height={600}
/>
<div class="thumbnails">
{product.value.images.map((img: string, i: number) => (
<img
key={i}
src={img}
alt={`${product.value.name} ${i + 1}`}
width={80}
height={80}
class={selectedImage.value === i ? 'active' : ''}
onClick$={() => selectedImage.value = i}
/>
))}
</div>
</div>

{/* product information */}
<div class="product-info">
<h1>{product.value.name}</h1>
<p class="price">
${product.value.price.toFixed(2)}
</p>

{/* variant selection */}
{product.value.variants && (
<div class="variants">
<label>Select option</label>
{product.value.variants.map((v: any) => (
<button
key={v.id}
class={selectedVariant.value === v.id ? 'selected' : ''}
disabled={v.stock === 0}
onClick$={() => selectedVariant.value = v.id}
>
{v.name} {v.stock === 0 ? '(Out of stock)' : ''}
</button>
))}
</div>
)}

{/* quantity selection */}
<div class="quantity">
<button onClick$={() => quantity.value = Math.max(1, quantity.value - 1)}>-</button>
<span>{quantity.value}</span>
<button onClick$={() => quantity.value = Math.min(99, quantity.value + 1)}>+</button>
</div>

{/* add to cart */}
<Form action={addToCart}>
<input type="hidden" name="productId" value={product.value.id} />
<input type="hidden" name="quantity" value={quantity.value} />
{selectedVariant.value && (
<input type="hidden" name="variant" value={selectedVariant.value} />
)}
<button
type="submit"
disabled={addToCart.isRunning}
class="add-to-cart"
>
{addToCart.isRunning ? 'Adding...' : 'Add to cart'}
</button>
</Form>

{addToCart.value?.success && (
<div class="success-message">Added to cart!</div>
)}

<div class="description"
dangerouslySetInnerHTML={product.value.description}
/>
</div>

{/* related products */}
<section class="related-products">
<h2>Related products</h2>
<div class="product-grid">
{related.value.map((item: any) => (
<Link key={item.id} href={`/shop/${item.id}`} class="product-card">
<img src={item.thumbnail} alt={item.name} width={200} height={200} />
<h3>{item.name}</h3>
<p>${item.price.toFixed(2)}</p>
</Link>
))}
</div>
</section>
</div>
);
});

export const head: DocumentHead = ({ resolveValue }) => {
const product = resolveValue(useProduct);
return {
title: `${product.name} - My Shop`,
meta: [
{ name: 'description', content: product.shortDescription },
{ property: 'og:title', content: product.name },
{ property: 'og:image', content: product.images[0] },
{ property: 'product:price:amount', content: String(product.price) },
{ property: 'product:price:currency', content: 'USD' },
],
};
};

Ch19 Full Summary​

ChapterTopicKey concepts
19.1Introduction to QwikResumability, O(1) loading, $ sign internals
19.2Environment setupQwikCity project, Optimizer, TypeScript
19.3Core conceptsuseSignal, useStore, useTask$, useResource$
19.4Qwik CityFile routing, routeLoader$, routeAction$, API
19.5Pro tipsClosures, deployment, ecosystem, performance optimization

Qwik's Strengths Summary​

1. O(1) initial loading — independent of app size
2. Zero hydration — resumability
3. Fine-grained reactivity — only necessary DOM nodes update
4. Full TypeScript support
5. Full-stack (QwikCity) — routeLoader$, routeAction$, API routes
6. Progressive Enhancement — forms and links work without JS
7. Service worker-based prefetching
8. Native Cloudflare Workers support

Congratulations on Completing the JavaScript Curriculum!​

You've done it — all 19 chapters of the JavaScript curriculum are complete. That's a real achievement!

Your Full Learning Journey​

Ch1  - JavaScript Basics             (variables, types, operators)
Ch2 - Variables, Types, Operators (ES6+ syntax)
Ch3 - Control Flow and Iteration (conditionals, loops, array methods)
Ch4 - Advanced Functions (closures, this, generators)
Ch5 - Objects, Classes, Prototypes (OOP, Symbol, Proxy)
Ch6 - Async Programming (Promise, async/await, event loop)
Ch7 - Modern JavaScript (ES2020-2024, regex)
Ch8 - Browser APIs and DOM (DOM, events, Fetch, Storage)
Ch9 - Node.js in Practice (HTTP, Express, streams)
Ch10 - React Basics (components, hooks, JSX)
Ch11 - Advanced React (advanced hooks, state management, performance)
Ch12 - Next.js Basics (App Router, RSC, data fetching)
Ch13 - Advanced Next.js (Server Actions, auth, deployment)
Ch14 - Vue.js 3 (Composition API, Pinia, Vue Router)
Ch15 - Nuxt 3 (file routing, SSR, deployment)
Ch16 - Angular (components, DI, RxJS, Signals)
Ch17 - Svelte 5 (Runes, SvelteKit, reactivity)
Ch18 - Solid.js (fine-grained reactivity, SolidStart)
Ch19 - Qwik (resumability, O(1) loading, QwikCity) ← you are here!

You now understand the full landscape of JavaScript front-end development. Here are some directions to consider:

Advanced topics

  • Advanced TypeScript (generics, advanced types, decorators)
  • Testing (Vitest, Playwright, Testing Library)
  • Performance optimization (Core Web Vitals, bundle optimization)
  • WebAssembly (Rust + wasm-bindgen)

Specialization paths

  • Full-stack: Node.js + Prisma + tRPC + Next.js
  • Mobile: React Native or Ionic
  • Desktop: Tauri (Rust + Qwik/React)
  • 3D/Games: Three.js, Babylon.js
  • AI/ML: TensorFlow.js, Transformers.js

Career

  • Open source contributions (Qwik GitHub: github.com/BuilderIO/qwik)
  • Complete a portfolio project
  • Start a technical blog
  • Give a conference talk

We sincerely applaud you for completing this long journey. Go build something that changes the world!