Esta página se muestra en inglés. Una traducción al español está en curso.
ScriptsApr 11, 2026·3 min de lectura

Qwik — Instant-Loading Web Apps Without Effort

Qwik is a web framework designed for instant-loading apps through resumability. Zero hydration, fine-grained code loading, and O(1) startup time — regardless of app size.

Introducción

Qwik is a new kind of web framework designed for the best possible time-to-interactive metric by focusing on resumability of server-side rendering of HTML, and fine-grained lazy-loading of code. Unlike traditional frameworks that need to "hydrate" (re-execute all component code on the client), Qwik can "resume" execution from where the server left off — resulting in instant page loads regardless of app complexity.

With 22K+ GitHub stars and MIT license, Qwik was created by Misko Hevery (also creator of Angular). It represents a fundamentally new approach to building web apps that scales to massive applications without sacrificing startup performance.

What Qwik Does

  • Resumability: Skip hydration entirely — apps resume from server state
  • O(1) Time-to-Interactive: Startup time independent of app size
  • Fine-Grained Lazy Loading: Auto-split code at function boundaries
  • Zero JS on Initial Load: Interactive pages with 0 JavaScript
  • React-like Syntax: JSX and component model familiar to React devs
  • Qwik City: Meta-framework with routing, layouts, server actions
  • SSR/SSG/Static: Flexible rendering modes
  • Optimized Bundles: Automatic code splitting at event handler level
  • Signal-based: Fine-grained reactivity like SolidJS

Key Concept: Resumability

Traditional Hydration (React, Vue, etc.):
  Server: Render HTML
  Client: Download JS  Execute all components  Attach listeners  Interactive
  Time: Proportional to app size

Qwik Resumability:
  Server: Render HTML + serialize state
  Client: No JS execution needed
  User clicks button  Load ONLY that handler  Execute
  Time: O(1) - constant regardless of app size

Getting Started

Create Project

npm create qwik@latest

# Choose:
# - my-app (name)
# - Basic App (template)
# - Select Qwik City (meta framework)

Project Structure

my-app/
├── src/
│   ├── routes/           ← File-based routing
│   │   ├── index.tsx
│   │   ├── about/
│   │   │   └── index.tsx
│   │   └── blog/
│   │       └── [slug]/
│   │           └── index.tsx
│   ├── components/
│   │   └── header/
│   │       └── header.tsx
│   ├── root.tsx         ← Root component
│   └── entry.ssr.tsx    ← SSR entry
├── vite.config.ts
└── package.json

Basic Components

Counter Component

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

export default component$(() => {
  const count = useSignal(0);

  return (
    <div>
      <h1>Counter: {count.value}</h1>
      <button onClick$={() => count.value++}>
        Increment
      </button>
    </div>
  );
});

Note: component$ and onClick$ — the $ marker tells Qwik to split this code for lazy loading.

Important: Only Runs on Interaction

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

export default component$(() => {
  const count = useSignal(0);

  // This console.log runs on server during SSR
  console.log('Component setup');

  return (
    <div>
      <p>Count: {count.value}</p>
      <button onClick$={() => {
        // THIS CODE is lazy-loaded ONLY when button clicked
        // Not included in initial JS bundle
        count.value++;
      }}>
        Click
      </button>
    </div>
  );
});

Async Data with useResource$

import { component$, useResource$, Resource } from '@builder.io/qwik';

export default component$(() => {
  const postsResource = useResource$(async () => {
    const res = await fetch('https://api.example.com/posts');
    return res.json();
  });

  return (
    <Resource
      value={postsResource}
      onPending={() => <div>Loading...</div>}
      onRejected={(error) => <div>Error: {error.message}</div>}
      onResolved={(posts) => (
        <ul>
          {posts.map((post) => <li key={post.id}>{post.title}</li>)}
        </ul>
      )}
    />
  );
});

Qwik City (Meta Framework)

File-Based Routing

// src/routes/blog/[slug]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';

// Runs on server before rendering
export const useBlogPost = routeLoader$(async ({ params }) => {
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  return post;
});

export default component$(() => {
  const post = useBlogPost();

  return (
    <article>
      <h1>{post.value?.title}</h1>
      <div dangerouslySetInnerHTML={post.value?.content} />
    </article>
  );
});

Server Actions

import { component$ } from '@builder.io/qwik';
import { Form, routeAction$, z, zod$ } from '@builder.io/qwik-city';

// Server-side action
export const useCreatePost = routeAction$(
  async (data, { redirect }) => {
    const post = await db.post.create({ data });
    throw redirect(302, `/blog/${post.slug}`);
  },
  zod$({
    title: z.string().min(1),
    content: z.string().min(10),
  })
);

export default component$(() => {
  const createPost = useCreatePost();

  return (
    <Form action={createPost}>
      <input name="title" placeholder="Title" />
      <textarea name="content" placeholder="Content" />
      <button type="submit">Create Post</button>
      {createPost.value?.failed && (
        <p>Errors: {JSON.stringify(createPost.value.fieldErrors)}</p>
      )}
    </Form>
  );
});

Layouts

// src/routes/layout.tsx
import { component$, Slot } from '@builder.io/qwik';

export default component$(() => {
  return (
    <>
      <header>My Site</header>
      <main>
        <Slot /> {/* Child routes render here */}
      </main>
      <footer>© 2024</footer>
    </>
  );
});

Performance Characteristics

Initial Page Load (Traditional React):
  HTML: 10KB
  JS: 300KB+ (React + app code)
  Time-to-Interactive: 2-5 seconds

Initial Page Load (Qwik):
  HTML: 15KB (includes serialized state)
  JS: 0KB (nothing to execute!)
  Time-to-Interactive: ~0 seconds

When user interacts:
  Download 3KB chunk for specific handler
  Execute handler
  Update DOM

Why Resumability?

Problem with hydration:

1. Server renders HTML (fast)
2. User sees content
3. Client downloads framework + components (slow)
4. Client RE-EXECUTES all components (builds VDOM)
5. Reconciles with existing DOM
6. Attaches event listeners
7. NOW interactive (1-5 seconds later)

Hydration = doing everything twice

Qwik solves this by:

1. Server renders HTML + serializes component state
2. Client sees content
3. Client needs ZERO JavaScript to be interactive
4. On first user interaction, download ONLY the specific handler
5. Execute handler, update DOM
6. Done

Qwik vs Alternatives

Feature Qwik React Solid Astro
Hydration None (Resume) Required Required Islands
Time-to-Interactive O(1) O(n) O(n) O(islands)
Initial JS 0KB Full bundle Full bundle Minimal
Syntax JSX + $ markers JSX JSX JSX/MD
Learning curve Medium Easy Easy Easy
Best for Large apps General Performance Content sites

FAQ

Q: What does the $ symbol in Qwik mean? A: $ is Qwik's "lazy loading boundary" marker. Each function marked with $ is compiled into an independent chunk that loads on demand. This is the core mechanism behind Qwik's fine-grained code splitting.

Q: Qwik or Astro — which should I choose? A: Astro uses "island architecture" — static HTML + a few interactive islands, great for content sites. Qwik makes the entire app resumable, suited to highly interactive web applications. Pick Astro for content blogs and Qwik for SaaS applications.

Q: Is the ecosystem mature? A: Qwik is relatively new (released in 2022) and its ecosystem is still growing. But the core team (including Angular's creator) is highly active, and large companies like Builder.io use it in production. Well worth considering for new projects that want maximum performance.

Sources & Credits

Discusión

Inicia sesión para unirte a la discusión.
Aún no hay comentarios. Sé el primero en compartir tus ideas.

Activos relacionados