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
| Chapter | Topic | Key concepts |
|---|---|---|
| 19.1 | Introduction to Qwik | Resumability, O(1) loading, $ sign internals |
| 19.2 | Environment setup | QwikCity project, Optimizer, TypeScript |
| 19.3 | Core concepts | useSignal, useStore, useTask$, useResource$ |
| 19.4 | Qwik City | File routing, routeLoader$, routeAction$, API |
| 19.5 | Pro tips | Closures, 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!
Recommended Next Steps
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!