Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/floppy-otters-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/router': minor
---

feat: add configurable Link component prefetch data strategies
44 changes: 43 additions & 1 deletion packages/docs/src/routes/api/qwik-router/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,48 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/link-component.tsx",
"mdFile": "router.link.md"
},
{
"name": "LinkDataCoarsePrefetchStrategy",
"id": "linkdatacoarseprefetchstrategy",
"hierarchy": [
{
"name": "LinkDataCoarsePrefetchStrategy",
"id": "linkdatacoarseprefetchstrategy"
}
],
"kind": "TypeAlias",
"content": "Defines the coarse pointer prefetching strategy for link data to enhance navigation performance. The strategies include:\n\n- `'viewport'`<!-- -->: Prefetch when the link becomes visible in the viewport. - `'pointerdown'`<!-- -->: Prefetch when the user presses the mouse button down on the link. - `'focus'`<!-- -->: Prefetch when the link receives focus.\n\n\n```typescript\nexport type LinkDataCoarsePrefetchStrategy = LinkDataBasicPrefetchStrategy;\n```",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.linkdatacoarseprefetchstrategy.md"
},
{
"name": "LinkDataFinePrefetchStrategy",
"id": "linkdatafineprefetchstrategy",
"hierarchy": [
{
"name": "LinkDataFinePrefetchStrategy",
"id": "linkdatafineprefetchstrategy"
}
],
"kind": "TypeAlias",
"content": "Defines the fine pointer prefetching strategy for link data to enhance navigation performance. The strategies include:\n\n- `'viewport'`<!-- -->: Prefetch when the link becomes visible in the viewport. - `'hover'`<!-- -->: Prefetch when the user hovers over the link. - `'pointerdown'`<!-- -->: Prefetch when the user presses the mouse button down on the link. - `'focus'`<!-- -->: Prefetch when the link receives focus.\n\n\n```typescript\nexport type LinkDataFinePrefetchStrategy = LinkDataBasicPrefetchStrategy | 'hover';\n```",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.linkdatafineprefetchstrategy.md"
},
{
"name": "LinkDataPrefetchOptions",
"id": "linkdataprefetchoptions",
"hierarchy": [
{
"name": "LinkDataPrefetchOptions",
"id": "linkdataprefetchoptions"
}
],
"kind": "Interface",
"content": "Specifies when link data should be prefetched to improve navigation performance.\n\n\n```typescript\nexport interface LinkDataPrefetchOptions \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\ncoarsePointer?\n\n\n</td><td>\n\n\n</td><td>\n\n[LinkDataCoarsePrefetchStrategy](#linkdatacoarseprefetchstrategy)<!-- -->\\[\\]\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\nfinePointer?\n\n\n</td><td>\n\n\n</td><td>\n\n[LinkDataFinePrefetchStrategy](#linkdatafineprefetchstrategy)<!-- -->\\[\\]\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.linkdataprefetchoptions.md"
},
{
"name": "LinkProps",
"id": "linkprops",
Expand All @@ -474,7 +516,7 @@
}
],
"kind": "Interface",
"content": "```typescript\nexport interface LinkProps extends AnchorAttributes \n```\n**Extends:** AnchorAttributes\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nprefetch?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean \\| 'js'\n\n\n</td><td>\n\n_(Optional)_ \\*\\*Defaults to \\_true\\_.\\*\\*\n\nWhether Qwik should prefetch and cache the target page of this \\*\\*`Link`<!-- -->\\*\\*, this includes invoking any \\*\\*`routeLoader$`<!-- -->\\*\\*, \\*\\*`onGet`<!-- -->\\*\\*, etc.\n\nThis \\*\\*improves UX performance\\*\\* for client-side (\\*\\*SPA\\*\\*) navigations.\n\nPrefetching occurs when a the Link enters the viewport in production (\\*\\*`q-e:qvisible`<!-- -->\\*\\*), or with \\*\\*`mouseover`<!-- -->/`focus`<!-- -->\\*\\* during dev.\n\nPrefetching will not occur if the user has the \\*\\*data saver\\*\\* setting enabled.\n\nSetting this value to \\*\\*`\"js\"`<!-- -->\\*\\* will prefetch only javascript bundles required to render this page on the client, \\*\\*`false`<!-- -->\\*\\* will disable prefetching altogether.\n\n\n</td></tr>\n<tr><td>\n\nreload?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\nreplaceState?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\nscroll?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>",
"content": "```typescript\nexport interface LinkProps extends AnchorAttributes \n```\n**Extends:** AnchorAttributes\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nprefetch?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean \\| 'js'\n\n\n</td><td>\n\n_(Optional)_ \\*\\*Defaults to \\_true\\_.\\*\\*\n\nWhether Qwik should prefetch and cache the target page of this \\*\\*`Link`<!-- -->\\*\\*, this includes invoking any \\*\\*`routeLoader$`<!-- -->\\*\\*, \\*\\*`onGet`<!-- -->\\*\\*, etc.\n\nThis \\*\\*improves UX performance\\*\\* for client-side (\\*\\*SPA\\*\\*) navigations.\n\nThis only changes when route data is fetched. It does not change how Qwik preloads the javascript needed for the next page.\n\nRoute data prefetching occurs based on the active prefetch strategy.\n\nBy default, fine pointers use \\*\\*`hover`<!-- -->\\*\\* and coarse pointers use \\*\\*`viewport`<!-- -->\\*\\*. Use \\*\\*`prefetchDataStrategy`<!-- -->\\*\\* to customize this per link, or the router's \\*\\*`linkDataPrefetch`<!-- -->\\*\\* option to change the app-wide defaults.\n\nPrefetching will not occur if the user has the \\*\\*data saver\\*\\* setting enabled.\n\nSetting this value to \\*\\*`\"js\"`<!-- -->\\*\\* will prefetch only javascript bundles required to render this page on the client, \\*\\*`false`<!-- -->\\*\\* will disable prefetching altogether.\n\n\n</td></tr>\n<tr><td>\n\nprefetchDataStrategy?\n\n\n</td><td>\n\n\n</td><td>\n\n[LinkDataPrefetchOptions](#linkdataprefetchoptions)\n\n\n</td><td>\n\n_(Optional)_ Specifies when route data should be prefetched to improve navigation performance.\n\nDefaults to `{ coarsePointer: ['viewport'], finePointer: ['hover'] }`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nreload?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\nreplaceState?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\nscroll?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/link-component.tsx",
"mdFile": "router.linkprops.md"
},
Expand Down
108 changes: 107 additions & 1 deletion packages/docs/src/routes/api/qwik-router/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,91 @@ Link: import("@qwik.dev/core").Component<LinkProps>;

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/link-component.tsx)

<h2 id="linkdatacoarseprefetchstrategy">LinkDataCoarsePrefetchStrategy</h2>

Defines the coarse pointer prefetching strategy for link data to enhance navigation performance. The strategies include:

- `'viewport'`: Prefetch when the link becomes visible in the viewport. - `'pointerdown'`: Prefetch when the user presses the mouse button down on the link. - `'focus'`: Prefetch when the link receives focus.

```typescript
export type LinkDataCoarsePrefetchStrategy = LinkDataBasicPrefetchStrategy;
```

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)

<h2 id="linkdatafineprefetchstrategy">LinkDataFinePrefetchStrategy</h2>

Defines the fine pointer prefetching strategy for link data to enhance navigation performance. The strategies include:

- `'viewport'`: Prefetch when the link becomes visible in the viewport. - `'hover'`: Prefetch when the user hovers over the link. - `'pointerdown'`: Prefetch when the user presses the mouse button down on the link. - `'focus'`: Prefetch when the link receives focus.

```typescript
export type LinkDataFinePrefetchStrategy =
| LinkDataBasicPrefetchStrategy
| "hover";
```

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)

<h2 id="linkdataprefetchoptions">LinkDataPrefetchOptions</h2>

Specifies when link data should be prefetched to improve navigation performance.

```typescript
export interface LinkDataPrefetchOptions
```

<table><thead><tr><th>

Property

</th><th>

Modifiers

</th><th>

Type

</th><th>

Description

</th></tr></thead>
<tbody><tr><td>

coarsePointer?

</td><td>

</td><td>

[LinkDataCoarsePrefetchStrategy](#linkdatacoarseprefetchstrategy)[]

</td><td>

_(Optional)_

</td></tr>
<tr><td>

finePointer?

</td><td>

</td><td>

[LinkDataFinePrefetchStrategy](#linkdatafineprefetchstrategy)[]

</td><td>

_(Optional)_

</td></tr>
</tbody></table>

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)

<h2 id="linkprops">LinkProps</h2>

```typescript
Expand Down Expand Up @@ -1167,7 +1252,11 @@ Whether Qwik should prefetch and cache the target page of this \*\*`Link`\*\*, t

This \*\*improves UX performance\*\* for client-side (\*\*SPA\*\*) navigations.

Prefetching occurs when a the Link enters the viewport in production (\*\*`q-e:qvisible`\*\*), or with \*\*`mouseover`/`focus`\*\* during dev.
This only changes when route data is fetched. It does not change how Qwik preloads the javascript needed for the next page.

Route data prefetching occurs based on the active prefetch strategy.

By default, fine pointers use \*\*`hover`\*\* and coarse pointers use \*\*`viewport`\*\*. Use \*\*`prefetchDataStrategy`\*\* to customize this per link, or the router's \*\*`linkDataPrefetch`\*\* option to change the app-wide defaults.

Prefetching will not occur if the user has the \*\*data saver\*\* setting enabled.

Expand All @@ -1176,6 +1265,23 @@ Setting this value to \*\*`"js"`\*\* will prefetch only javascript bundles requi
</td></tr>
<tr><td>

prefetchDataStrategy?

</td><td>

</td><td>

[LinkDataPrefetchOptions](#linkdataprefetchoptions)

</td><td>

_(Optional)_ Specifies when route data should be prefetched to improve navigation performance.

Defaults to `{ coarsePointer: ['viewport'], finePointer: ['hover'] }`.

</td></tr>
<tr><td>

reload?

</td><td>
Expand Down
46 changes: 46 additions & 0 deletions packages/docs/src/routes/docs/(qwik)/advanced/vite/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,52 @@ mdx?: any;
platform?: Record<string, unknown>;
```

#### `linkDataPrefetch`

```ts
/**
* Configure the default route data prefetch behavior used by `<Link />`.
*/
linkDataPrefetch?: {
coarsePointer?: ('viewport' | 'focus' | 'pointerdown')[];
finePointer?: ('viewport' | 'hover' | 'focus' | 'pointerdown')[];
};
```

This lets you change the application-wide defaults that `Link` uses when no `prefetchDataStrategy` prop is provided.

This option only changes route data prefetching. It does not change how Qwik preloads the
javascript needed for the next page.

By default, Qwik Router uses:

```ts
{
coarsePointer: ['viewport'],
finePointer: ['hover'],
}
```

For example, you can delay prefetching until interaction:

```ts title="vite.config.ts"
import { defineConfig } from 'vite';
import { qwikRouter } from '@qwik.dev/router/vite';

export default defineConfig(() => {
return {
plugins: [
qwikRouter({
linkDataPrefetch: {
coarsePointer: ['pointerdown'],
finePointer: ['hover', 'focus'],
},
}),
],
};
});
```

## Troubleshooting

Before creating an issue on the Qwik repository please check the `Troubleshooting` section of the [Vite documentation](https://vitejs.dev/guide/troubleshooting.html) and make sure you're using the recommended version.
17 changes: 15 additions & 2 deletions packages/docs/src/routes/docs/(qwikrouter)/api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -608,13 +608,17 @@ Whether Qwik should prefetch and cache the target page of this `Link`, this incl

This **improves UX performance** for client-side (**SPA**) navigations.

Prefetching occurs when a the Link enters the viewport in production (`q-e:qvisible`), or with `mouseover`/`focus` during dev.
This only changes when route data is fetched. It does not change how Qwik preloads the javascript needed for the next page.

Route data prefetching occurs based on the active strategy.

By default, fine pointers use `hover` and coarse pointers use `viewport`. You can override this per link with `prefetchDataStrategy`, or set app-wide defaults with the router's `linkDataPrefetch` option.

Prefetching will not occur if the user has the **data saver** setting enabled.

Setting this value to `"js"` will prefetch only javascript bundles required to render this page on the client, `false` will disable prefetching altogether.

<Note title="Warning">If you have a menu with many links, all of them will be loaded immediately when you enter the production page, which may result with too many requests</Note>
<Note title="Warning">If you use `viewport` route data prefetching for many visible links, the browser may start many `q-data.json` requests at once.</Note>

```tsx
import { component$ } from '@qwik.dev/core';
Expand All @@ -632,6 +636,15 @@ export default component$(() => {
<Link href="/c">
page content & js will be prefetched
</Link>
<Link
href="/d"
prefetchDataStrategy={{
coarsePointer: ['pointerdown'],
finePointer: ['hover', 'focus'],
}}
>
page prefetch timing is customized
</Link>
</div>
);
});
Expand Down
47 changes: 45 additions & 2 deletions packages/docs/src/routes/docs/(qwikrouter)/routing/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,56 @@ export default component$(() => {

### `<Link prefetch>`

By default, a `Link` component will start prefetching the next page as soon as the user hovers over the corresponding link in the UI. So if the application is done prefetching when the user clicks on the link, the next page will appear instantly. Although Qwik applications already excel at lazy loading javascript, this behavior can come in handy for content-heavy pages or SSR pages that need to wait for database or API calls.
By default, a `Link` component will prefetch differently depending on the user's input device:

- fine pointers such as a mouse use `['hover']`
- coarse pointers such as touchscreens use `['viewport']`

So on desktop, route data is usually fetched when a link is hovered, while on touch devices it is fetched when the link becomes visible. If the application is done prefetching when the user activates the link, the next page can appear instantly. Although Qwik applications already excel at lazy loading javascript, this behavior can be especially helpful for content-heavy pages or SSR pages that need to wait for database or API calls.

If this is not your desired behavior, you can set the `prefetch` prop to false.
```tsx
<Link prefetch={false} href="/about">About</Link>
<Link prefetch={false} href="/about">
About
</Link>
```

You can also set `prefetch="js"` to prefetch only the javascript bundles required for the route, without prefetching route data.

```tsx
<Link prefetch="js" href="/about">
About
</Link>
```

### `<Link prefetchDataStrategy>`

Use the `prefetchDataStrategy` prop to override when a specific link should prefetch route data.

```tsx
<Link
href="/pricing"
prefetchDataStrategy={{
coarsePointer: ['pointerdown'],
finePointer: ['hover', 'focus'],
}}
>
Pricing
</Link>
```

This only affects `loadClientData()` prefetching. It does not change how Qwik preloads the
javascript needed for the next page.

Available strategy values are:

- `viewport` to prefetch when the link becomes visible
- `hover` to prefetch on mouse hover for fine pointers
- `focus` to prefetch when the link receives keyboard focus
- `pointerdown` to prefetch as soon as the user presses the link

An empty array disables prefetching for that pointer type.

### Scroll Restoration

Qwik provides best-in-class scroll restoration for SPA's that closely mimics the native browser experience.
Expand Down
5 changes: 5 additions & 0 deletions packages/qwik-router/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ declare var _qwikActionsMap: Map<string, ActionInternal> | undefined;

type ExperimentalFeatures = import('@qwik.dev/core/optimizer').ExperimentalFeatures;

type LinkDataPrefetchOptions = import('./src/runtime/src/types').LinkDataPrefetchOptions;

declare var __EXPERIMENTAL__: {
[K in ExperimentalFeatures]: boolean;
};
Expand All @@ -22,6 +24,9 @@ declare var __DEFAULT_LOADERS_SERIALIZATION_STRATEGY__: SerializationStrategy;
/** Should routes not have a trailing slash? */
declare var __NO_TRAILING_SLASH__: boolean;

/** Specifies when link resources should be prefetched to improve navigation performance. */
declare var __LINK_DATA_PREFETCH_STRATEGY__: LinkDataPrefetchOptions;

/** Maximum number of SSR-rendered pages to keep in the in-memory cache. */
declare var __SSR_CACHE_SIZE__: number;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { NormalizedPluginOptions } from '../types';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { assert, test } from 'vitest';
import { DEFAULT_LINK_DATA_PREFETCH_STRATEGY } from '../../runtime/src/link-prefetch-strategy';

const routesDir = tmpdir();
const serverPluginsDir = tmpdir();
Expand Down Expand Up @@ -80,6 +81,7 @@ const menuFilePath = join(routesDir, 'docs', 'menu.md');
platform: {},
rewriteRoutes: [],
defaultLoadersSerializationStrategy: 'never',
linkDataPrefetch: DEFAULT_LINK_DATA_PREFETCH_STRATEGY,
};
globalThis.__NO_TRAILING_SLASH__ = !t.trailingSlash;
assert.equal(getMarkdownRelativeUrl(opts, menuFilePath, t.href), t.expect);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assert, test } from 'vitest';
import type { NormalizedPluginOptions, RouteSourceFile } from '../types';
import { resolveLayout } from './resolve-source-file';
import { getSourceFile } from './source-file';
import { DEFAULT_LINK_DATA_PREFETCH_STRATEGY } from '../../runtime/src/link-prefetch-strategy';

test('resolveLayout', () => {
const t = [
Expand Down Expand Up @@ -49,6 +50,7 @@ test('resolveLayout', () => {
platform: {},
rewriteRoutes: [],
defaultLoadersSerializationStrategy: 'never',
linkDataPrefetch: DEFAULT_LINK_DATA_PREFETCH_STRATEGY,
};
const sourceFile: RouteSourceFile = {
...getSourceFile(c.fileName)!,
Expand Down
Loading
Loading