Live page: giuliomarinelli.com
Tested on Mobile Lighthouse
I am currently rebuilding my personal website, but I did not want the domain to remain empty while the full project was still under construction.
The obvious solution would have been a minimal placeholder: a logo, a short message and perhaps a contact link. Instead, I treated the temporary landing page as the first production release of the future website.
It had to be:
- multilingual;
- accessible;
- indexable;
- visually stable;
- inexpensive to deliver;
- lightweight in the browser;
- easy to evolve into the complete site.
The result is a small Qwik application deployed on Cloudflare Pages.
In a mobile Lighthouse run, the page reached:
| Category | Result |
|---|---|
| Performance | 100 |
| Accessibility | 100 |
| Best Practices | 100 |
| SEO | 100 |
| Agentic Browsing | 3/3 |
The main synthetic performance metrics were:
| Metric | Result |
|---|---|
| First Contentful Paint | 1.4 s |
| Largest Contentful Paint | 1.4 s |
| Speed Index | 1.5 s |
| Total Blocking Time | 30 ms |
| Cumulative Layout Shift | 0 |
Those scores are satisfying, but the interesting part is not the green circles themselves.
The interesting question is:
Where was unnecessary work removed?
The answer exists at two different layers.
Qwik reduces the amount of work required to start the application in the browser. Cloudflare reduces the distance, repeated transfers and origin work required to deliver it.
Less JavaScript. Less distance.
That is the idea behind this project.
The stack
At the time of writing, the landing page uses:
- Qwik and Qwik City;
- TypeScript;
- Tailwind CSS;
- Vite;
- static generation for the localized pages;
- a narrowly scoped Cloudflare Pages Function;
- Cloudflare Pages for deployment and delivery.
The application has two canonical localized routes:
/it/
/en/
Both pages are generated during the build. The root route is the only dynamic part:
/
It reads the visitor's Accept-Language header and redirects to the most appropriate localized URL.
The architecture can be summarized as:
Request to /
|
v
Cloudflare Pages Function
|
+-- reads Accept-Language
|
+-- redirects to /it/ or /en/
|
v
pre-rendered HTML
|
v
Qwik resumes when needed
This keeps runtime computation limited to the one place where it adds value.
Performance is a pipeline
It is tempting to describe web performance as a frontend-only concern. In practice, a page has to pass through several stages before a visitor can use it:
- the server or build system must produce the response;
- the response must travel through the network;
- the browser must parse the HTML and CSS;
- assets must be downloaded or recovered from cache;
- JavaScript may need to be downloaded, parsed and executed;
- the application may need to reconstruct client-side state;
- the browser must render a stable interface.
A fast framework cannot compensate for unnecessarily distant or repeatedly transferred assets. A powerful CDN cannot compensate for an application that blocks the main thread while booting a large client-side runtime.
Qwik and Cloudflare address different parts of the same path.
Server-rendered HTML does not eliminate startup cost
Server-side rendering and static generation can make content visible quickly because the browser receives useful HTML instead of an empty application shell.
However, in a hydration-based architecture, the browser often still needs to perform significant startup work before the application becomes interactive.
Conceptually, the process looks like this:
Server renders HTML
|
v
Browser downloads application code
|
v
Components execute again
|
v
The component tree, listeners and state are reconstructed
|
v
The page becomes interactive
The server has already rendered the interface, but the client replays enough application logic to rebuild the framework's internal representation.
This reconstruction is hydration.
Hydration is not inherently wrong. It is a valid and widely used architecture. The trade-off is that its startup cost can increase with the amount of client-side code and the complexity of the page.
Qwik takes a different approach.
Qwik resumes instead of hydrating
The central idea behind Qwik is resumability.
Rather than rendering on the server and then rebuilding the application in the browser, Qwik serializes the information needed to continue execution from the state produced during server-side rendering or static generation.
That includes information related to:
- event listeners;
- component boundaries;
- application state;
- reactive relationships.
The browser does not need to eagerly execute every component merely to discover where listeners are located or how the component tree is structured.
A simplified mental model is:
Server renders and serializes the application
|
v
Browser receives ready-to-use HTML
|
v
Qwik resumes a specific part when an interaction requires it
This distinction matters because the fastest startup task is often the task that never needs to run.
Qwik's own documentation describes resumability as pausing execution on the server and continuing it on the client without replaying all application logic:
Why Qwik APIs end with $
A Qwik component commonly looks familiar to developers who have used JSX-based frameworks:
import { component$, useSignal } from "@builder.io/qwik";
export const Counter = component$(() => {
const count = useSignal(0);
return (
<button onClick$={() => count.value++}>
{count.value}
</button>
);
});
The trailing $ is not decorative.
It marks a lazy-loadable boundary that the Qwik Optimizer can transform into an independently importable symbol. The component body and the click handler can therefore become separate entry points.
The generated HTML can contain a reference to the code needed for an interaction. Qwik calls that reference a QRL, or Qwik URL.
Conceptually, an event reference may resemble:
<button on:click="./chunk.js#handler_symbol">
Click me
</button>
A small global loader can observe the event, locate the referenced symbol and load the relevant code.
The browser does not need to download and execute the whole component tree at startup just to discover that one button has a click handler.
The Optimizer and QRL model are explained in more detail here:
Lazy loading without waiting for the click
Lazy loading code only after an interaction could introduce a visible delay. Qwik can mitigate this through targeted module prefetching.
The useful distinction is between:
- downloading code;
- executing code.
A likely-to-be-used module can be downloaded in the background and placed in the browser cache without being executed during application startup. When the interaction happens, the required code may already be available locally.
This allows Qwik to avoid eagerly executing application code while still preparing probable interaction paths.
The relevant mechanism is described in the official documentation:
How the landing page uses Qwik
The landing page is composed from small, focused sections:
export const LandingPage = component$<LandingPageProps>(({ locale }) => {
const copy = LANDING_CONTENT[locale];
return (
<SiteShell locale={locale}>
<HeroSection copy={copy.hero} />
<AudienceSection copy={copy.audience} />
<ProblemsSection copy={copy.problems} />
<PreviewSection copy={copy.preview} />
<OffersSection copy={copy.offers} />
<MethodSection copy={copy.method} />
<QualitySection copy={copy.quality} />
<EditorialSection copy={copy.editorial} />
<ContactCtaSection copy={copy.contactCta} />
</SiteShell>
);
});
The Italian and English content share one strongly typed editorial structure:
export type LandingLocale = "it" | "en";
export const LANDING_CONTENT: Record<LandingLocale, LandingCopy> = {
it: {
// Italian content
},
en: {
// English content
},
};
This does more than prevent missing fields.
It ensures that both languages follow the same content model while the visual components remain independent from the copy. The page can evolve without duplicating an entire route implementation for each locale.
The localized routes are pre-rendered during the build. This means that a visitor requesting /it/ or /en/ receives complete HTML without requiring a server-side render for every visit.
Qwik's resumability files are still emitted, so the page can become interactive without adopting a traditional full-page hydration step.
The other half of performance: delivery
Producing efficient HTML is only half of the problem.
The page still has to reach the visitor.
A content delivery network stores and serves content from geographically distributed infrastructure. Instead of every request travelling to one central origin server, a cacheable resource can be delivered from a location closer to the user.
This can reduce latency and reduce repeated work for the origin.
Cloudflare describes its cache as a globally distributed network of data centers that stores frequently accessed content closer to users:
For this project, Cloudflare Pages also removes the need to manage a traditional web server for the static output. A deployment publishes the generated site and its assets through Cloudflare's platform.
The effect is complementary to Qwik:
Qwik:
reduce work during browser startup
Cloudflare:
reduce delivery distance and repeated retrieval
Edge cache and browser cache are not the same thing
When discussing caching, it is useful to separate at least two layers.
Edge cache
A Cloudflare data center may store a reusable response close to visitors.
Visitor
|
v
Cloudflare edge
|
+-- cache hit: return the stored response
|
+-- cache miss: retrieve the resource
A cache hit can avoid another trip to the underlying storage or origin.
Browser cache
The visitor's own browser may store an asset locally.
Page requests asset
|
v
Browser cache contains a fresh copy
|
v
No network transfer is required
Both layers reduce repeated work, but they operate in different places and are controlled by related but distinct policies.
Long-lived caching for versioned assets
The landing page defines explicit response headers for generated assets:
/assets/*
Cache-Control: public, max-age=31536000, immutable
/build/*
Cache-Control: public, max-age=31536000, immutable
/fonts/*
Cache-Control: public, max-age=31536000, immutable
A one-year cache lifetime is appropriate only when the URLs are safe to treat as immutable.
Generated build assets normally include a content-derived identifier in the filename:
/build/q-abc123.js
/build/q-def456.js
When the file content changes, the build emits a different URL. The old file can remain cached because the new HTML refers to the new filename.
This is the key principle:
Cache aggressively when the URL identifies the content.
The immutable directive tells the browser that the resource will not change during its freshness lifetime, avoiding unnecessary revalidation.
Cloudflare respects cacheable origin response headers such as public with a positive max-age, unless a cache rule changes the behavior:
Cloudflare: Default Cache Behavior
Not every resource should be cached for a year
The project also exposes text resources such as:
/llms.txt
/it/index.md
/en/index.md
These keep stable URLs even when their content changes, so they use a shorter lifetime:
Cache-Control: public, max-age=3600
That one-hour value is not universally correct; it is a project decision.
The important point is that cache policy follows resource semantics:
| Resource type | URL strategy | Cache strategy |
|---|---|---|
| Hashed JS, CSS and assets | URL changes with content | Long-lived and immutable |
| Text endpoints with stable URLs | URL remains constant | Shorter TTL |
| Request-dependent redirect | Response varies by visitor | Do not store |
The root route is deliberately not cached
The only dynamic route is /.
Its request handler reads the browser's language preferences and redirects to /it/ or /en/.
A simplified version is:
export const onRequest: RequestHandler = async (event) => {
if (event.url.pathname !== "/") {
await event.next();
return;
}
const locale = resolveLocaleFromAcceptLanguage(
event.request.headers.get("accept-language"),
);
event.headers.set("Vary", "Accept-Language");
event.headers.set("Cache-Control", "private, no-store");
throw event.redirect(302, `/${locale}/${event.url.search}`);
};
The implementation also parses language quality values such as:
it-IT,it;q=0.9,en;q=0.8
It selects a supported locale and falls back to Italian when no supported preference is available.
The response is intentionally marked:
Cache-Control: private, no-store
That prevents a shared cache from storing a redirect generated for one visitor and serving it to another visitor with different language preferences.
Vary: Accept-Language documents that the response depends on that request header. The no-store directive provides the stronger guarantee required by this implementation.
Cloudflare's cache documentation confirms that private and no-store responses are not eligible for normal shared caching:
Cloudflare: Origin Cache Control
This produces a useful rule:
Cache everything that is safely reusable. Avoid caching the parts whose meaning depends on the individual request.
Static by default, dynamic by exception
The project is not entirely static and not generally server-rendered.
It is hybrid in a deliberately narrow way:
/ dynamic language negotiation
/it/ pre-rendered localized page
/en/ pre-rendered localized page
/assets/* static, aggressively cached
/build/* static, aggressively cached
/fonts/* static, aggressively cached
This pattern avoids turning a small request-dependent decision into permanent runtime work for every page view.
The server-side function decides where the visitor should go. The selected page itself does not need to be recomputed.
Less work at every stage
The complete performance strategy can be summarized as follows:
| Stage | Work avoided |
|---|---|
| Build | Localized pages are rendered before requests arrive |
| Edge delivery | Static output is distributed through Cloudflare |
| Shared cache | Reusable assets do not require repeated retrieval |
| Browser cache | Immutable assets do not need to be downloaded again |
| Browser startup | Qwik avoids a traditional full hydration pass |
| Interactions | Code can be loaded and executed at fine-grained boundaries |
| Rendering | Stable dimensions prevent avoidable layout movement |
| Dynamic routing | Runtime logic is confined to the root language redirect |
No single row explains the Lighthouse score.
The result comes from repeatedly asking the same question:
Can this work be done earlier, done once, done closer to the visitor, deferred until necessary, or avoided completely?
What the Lighthouse score does not prove
A perfect Lighthouse score is useful feedback, but it has limits.
This result is a synthetic measurement from a relatively small landing page. It does not prove that:
- every Qwik application will automatically be fast;
- the page will behave identically on every real device and network;
- a score of 100 guarantees good search rankings;
- automated accessibility checks replace keyboard and screen-reader testing;
- future analytics, forms or third-party scripts will have no performance cost.
Real-user monitoring matters after launch. Performance also needs to be measured again as the complete website grows.
The current result should therefore be read as evidence that the architectural baseline is healthy, not as a permanent certificate.
What I learned
1. A temporary page can be a real engineering exercise
A coming-soon page does not have to be disposable. It can validate deployment, internationalization, caching, SEO, accessibility and component architecture before the complete site is ready.
2. Static and dynamic are not binary choices
A single project can combine pre-rendered content with narrowly scoped runtime behavior. The useful question is not “static or dynamic?” but “which parts genuinely require runtime computation?”
3. Framework and infrastructure solve different problems
Qwik cannot move a data center closer to the visitor. Cloudflare cannot remove unnecessary client-side application startup.
Used together, they reduce work at complementary layers.
4. Caching should follow content identity
An asset whose URL changes with its content can be cached aggressively. A response that changes while keeping the same URL needs a shorter lifetime or no storage at all.
5. Performance is usually architectural before it is tactical
Image compression and bundle inspection matter. But the largest gains often come from earlier decisions:
- pre-rendering instead of computing per request;
- serializing state instead of reconstructing it;
- loading code at granular boundaries;
- distributing content close to visitors;
- treating immutable and request-dependent resources differently.
Conclusion
Qwik and Cloudflare optimize different parts of the same journey.
Qwik reduces what the browser must execute to start and resume an application. Cloudflare reduces how far reusable content must travel and how often it must be retrieved.
The resulting speed does not come from one Lighthouse trick. It comes from systematically removing unnecessary work across the delivery pipeline.
The landing page is temporary.
The engineering principles behind it are not.
Have you compared Qwik’s resumability with a hydration-obased framework on a similar page? I’d be interested in seeing real-world measurements.

Top comments (0)