Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[breaking] ssr/hydrate/router/prerender.default are now configurable in +page(.server).js and +layout(.server).js #6197

Merged
merged 27 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7a617f9
[feat] allow ssr to be configurable in +page.js and +layout.js
dummdidumm Aug 23, 2022
0bb5cd3
fix tests + docs
dummdidumm Aug 23, 2022
812e1b5
Update documentation/docs/12-page-options.md
dummdidumm Aug 24, 2022
3b60c40
Update documentation/docs/12-page-options.md
dummdidumm Aug 24, 2022
9a001dd
Update documentation/docs/12-page-options.md
dummdidumm Aug 24, 2022
6059112
Merge branch 'master' into ssr-option
dummdidumm Aug 27, 2022
0a62ec8
remove browser options in favor of layout/page exports
dummdidumm Aug 27, 2022
4f61f59
remove ssr option from handle, update docs
dummdidumm Aug 27, 2022
b63114a
test
dummdidumm Aug 27, 2022
5f72158
update changelog
dummdidumm Aug 27, 2022
396fea1
make prerender option work in layouts
dummdidumm Aug 28, 2022
dba1c56
adjust test
dummdidumm Aug 28, 2022
ccb5fcb
update SEO docs
Rich-Harris Aug 28, 2022
320bd87
Merge branch 'master' into ssr-option
dummdidumm Aug 30, 2022
fb8efce
remove prerender.default
dummdidumm Aug 30, 2022
94f3077
how did i miss these
dummdidumm Aug 30, 2022
332913e
merge master
Rich-Harris Aug 30, 2022
f0a69c3
fix bad link
Rich-Harris Aug 30, 2022
7f38aed
fix docs
Rich-Harris Aug 30, 2022
3db97a0
tweak docs
Rich-Harris Aug 30, 2022
195b75f
i think this order is slightly more logical
Rich-Harris Aug 30, 2022
ac666ad
add detail about prerendering server routes
Rich-Harris Aug 30, 2022
014a1da
typo
Rich-Harris Aug 30, 2022
dff921a
put link first
Rich-Harris Aug 30, 2022
8de3d4c
small tweak
Rich-Harris Aug 30, 2022
4dcee25
Update documentation/docs/03-routing.md
dummdidumm Aug 30, 2022
d111a21
Revert "Update documentation/docs/03-routing.md"
Rich-Harris Aug 30, 2022
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/poor-gifts-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[breaking] `ssr/hydrate/router/prerender.default` are now configurable in `+page(.server).js` and `+layout(.server).js`
15 changes: 11 additions & 4 deletions documentation/docs/03-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ This function runs alongside `+page.svelte`, which means it runs on the server d

As well as `load`, `page.js` can export values that configure the page's behaviour:

- `export const prerender = true` or `false` overrides [`config.kit.prerender.default`](/docs/configuration#prerender)
- `export const hydrate = true` or `false` overrides [`config.kit.browser.hydrate`](/docs/configuration#browser)
- `export const router = true` or `false` overrides [`config.kit.browser.router`](/docs/configuration#browser)
- `export const prerender = true` or `false` or `'auto'`
- `export const hydrate = true` or `false`
- `export const router = true` or `false`
- `export const ssr = true` or `false`

You can find more information about these in [page options](/docs/page-options).

#### +page.server.js

Expand Down Expand Up @@ -108,6 +111,8 @@ export async function load({ params }) {

During client-side navigation, SvelteKit will load this data from the server, which means that the returned value must be serializable using [devalue](https://github.com/rich-harris/devalue).

Like `+page.js`, `+page.server.js` can export [page options](/docs/page-options) — `prerender`, `hydrate`, `router` and `ssr`.

#### Actions

`+page.server.js` can also declare _actions_, which correspond to the `POST`, `PATCH`, `PUT` and `DELETE` HTTP methods. A request made to the page with one of these methods will invoke the corresponding action before rendering the page.
Expand Down Expand Up @@ -277,7 +282,7 @@ export function load() {
}
```

Unlike `+page.js`, `+layout.js` cannot export `prerender`, `hydrate` and `router`, as these are page-level options.
If a `+layout.js` exports [page options](/docs/page-options) — `prerender`, `hydrate` `router` and `ssr` — they will be used as defaults for child pages.

Data returned from a layout's `load` function is also available to all its child pages:

Expand All @@ -297,6 +302,8 @@ Data returned from a layout's `load` function is also available to all its child

To run your layout's `load` function on the server, move it to `+layout.server.js`, and change the `LayoutLoad` type to `LayoutServerLoad`.

Like `+layout.js`, `+layout.server.js` can export [page options](/docs/page-options) — `prerender`, `hydrate` `router` and `ssr`.

### +server

As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file (or `+server.ts`) exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT` and `DELETE` that take a `RequestEvent` argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object.
Expand Down
4 changes: 0 additions & 4 deletions documentation/docs/06-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,20 @@ You can add call multiple `handle` functions with [the `sequence` helper functio

`resolve` also supports a second, optional parameter that gives you more control over how the response will be rendered. That parameter is an object that can have the following fields:

- `ssr: boolean` (default `true`) — if `false`, renders an empty 'shell' page instead of server-side rendering
- `transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined>` — applies custom transforms to HTML. If `done` is true, it's the final chunk. Chunks are not guaranteed to be well-formed HTML (they could include an element's opening tag but not its closing tag, for example) but they will always be split at sensible boundaries such as `%sveltekit.head%` or layout/page components.

```js
/// file: src/hooks.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
ssr: !event.url.pathname.startsWith('/admin'),
transformPageChunk: ({ html }) => html.replace('old', 'new')
});

return response;
}
```

> Disabling [server-side rendering](/docs/appendix#ssr) effectively turns your SvelteKit app into a [**single-page app** or SPA](/docs/appendix#csr-and-spa). In most situations this is not recommended ([see appendix](/docs/appendix#ssr)). Consider whether it's truly appropriate to disable it, and do so selectively rather than for all requests.

### handleError

If an error is thrown during loading or rendering, this function will be called with the `error` and the `event` that caused it. This allows you to send data to an error tracking service, or to customise the formatting before printing the error to the console.
Expand Down
86 changes: 55 additions & 31 deletions documentation/docs/12-page-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,27 @@
title: Page options
---

By default, SvelteKit will render any component first on the server and send it to the client as HTML. It will then render the component again in the browser to make it interactive in a process called **hydration**. For this reason, you need to ensure that components can run in both places. SvelteKit will then initialise a [**router**](/docs/routing) that takes over subsequent navigations.
By default, SvelteKit will render (or [prerender](/docs/appendix#prerendering)) any component first on the server and send it to the client as HTML. It will then render the component again in the browser to make it interactive in a process called **hydration**. For this reason, you need to ensure that components can run in both places. SvelteKit will then initialise a [**router**](/docs/routing) that takes over subsequent navigations.

You can control each of these on a per-app (via `svelte.config.js`) or per-page (via `+page.js` or `+page.server.js`) basis. If both are specified, per-page settings override per-app settings in case of conflicts.

### router

SvelteKit includes a [client-side router](/docs/appendix#routing) that intercepts navigations (from the user clicking on links, or interacting with the back/forward buttons) and updates the page contents, rather than letting the browser handle the navigation by reloading.

In certain circumstances you might need to disable [client-side routing](/docs/appendix#routing) with the app-wide [`browser.router` config option](/docs/configuration#browser) or the page-level `router` export:

```js
/// file: +page.js/+page.server.js
export const router = false;
```

Note that this will disable client-side routing for any navigation from this page, regardless of whether the router is already active.

### hydrate

Ordinarily, SvelteKit [hydrates](/docs/appendix#hydration) your server-rendered HTML into an interactive page. Some pages don't require JavaScript at all — many blog posts and 'about' pages fall into this category. In these cases you can skip hydration when the app boots up with the app-wide [`browser.hydrate` config option](/docs/configuration#browser) or the page-level `hydrate` export:

```js
/// file: +page.js/+page.server.js
export const hydrate = false;
```

> If `hydrate` and `router` are both `false`, SvelteKit will not add any JavaScript to the page at all. If [server-side rendering](/docs/hooks#handle) is disabled in `handle`, `hydrate` must be `true` or no content will be rendered.
You can control each of these on a page-by-page basis by exporting options from [`+page.js`](/docs/routing#page-page-js) or [`+page.server.js`](/docs/routing#page-page-server-js), or for groups of pages using a shared [`+layout.js`](/docs/routing#layout-layout-js) or [`+layout.server.js`](/docs/routing#layout-layout-server-js). To define an option for the whole app, export it from the root layout. Child layouts and pages override values set in parent layouts, so — for example — you can enable prerendering for your entire app then disable it for pages that need to be dynamically rendered.

### prerender

It's likely that at least some routes of your app can be represented as a simple HTML file generated at build time. These routes can be [_prerendered_](/docs/appendix#prerendering).

Prerendering happens automatically for any `+page` or `+server` file with the `prerender` annotation:

```js
/// file: +page.js/+page.server.js/+server.js
export const prerender = true;
```

Alternatively, you can set [`config.kit.prerender.default`](/docs/configuration#prerender) to `true` and prerender everything except pages that are explicitly marked as _not_ prerenderable:
Alternatively, you can set `export const prerender = true` in your root `+layout` and prerender everything except pages that are explicitly marked as _not_ prerenderable:

```js
/// file: +page.js/+page.server.js/+server.js
export const prerender = false;
```

Routes with `prerender = true` will be excluded from manifests used for dynamic SSR, making your server (or serverless/edge functions) smaller. In some cases you might want to prerender a route but also include it in the manifest (for example, you want to prerender your most recent/popular content but server-render the long tail) — for these cases, there's a third option, 'auto':
Routes with `prerender = true` will be excluded from manifests used for dynamic SSR, making your server (or serverless/edge functions) smaller. In some cases you might want to prerender a route but also include it in the manifest (for example, with a route like `/blog/[slug]` where you want to prerender your most recent/popular content but server-render the long tail) — for these cases, there's a third option, 'auto':

```js
/// file: +page.js/+page.server.js/+server.js
Expand All @@ -57,7 +31,24 @@ export const prerender = 'auto';

> If your entire app is suitable for prerendering, you can use [`adapter-static`](https://github.com/sveltejs/kit/tree/master/packages/adapter-static), which will output files suitable for use with any static webserver.

The prerenderer will start at the root of your app and generate HTML for any prerenderable pages it finds. Each page is scanned for `<a>` elements that point to other pages that are candidates for prerendering — because of this, you generally don't need to specify which pages should be accessed. If you _do_ need to specify which pages should be accessed by the prerenderer, you can do so with the `entries` option in the [prerender configuration](/docs/configuration#prerender).
The prerenderer will start at the root of your app and generate files for any prerenderable pages or `+server.js` routes it finds. Each page is scanned for `<a>` elements that point to other pages that are candidates for prerendering — because of this, you generally don't need to specify which pages should be accessed. If you _do_ need to specify which pages should be accessed by the prerenderer, you can do so with the `entries` option in the [prerender configuration](/docs/configuration#prerender).

#### Prerendering server routes

Unlike the other page options, `prerender` also applies to `+server.js` files. These files are _not_ affected from layouts, but will inherit default values from the pages that fetch data from them, if any. For example if a `+page.js` contains this `load` function...

```js
/// file: +page.js
export const prerender = true;

/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
const res = await fetch('/my-server-route.json');
return await res.json();
}
```

...then `src/routes/my-server-route.json/+server.js` will be treated as prerenderable if it doesn't contain its own `export const prerender = false`.

#### When not to prerender

Expand All @@ -76,3 +67,36 @@ Because prerendering writes to the filesystem, it isn't possible to have two end
For that reason among others, it's recommended that you always include a file extension — `src/routes/foo.json/+server.js` and `src/routes/foo/bar.json/+server.js` would result in `foo.json` and `foo/bar.json` files living harmoniously side-by-side.

For _pages_, we skirt around this problem by writing `foo/index.html` instead of `foo`.

### hydrate

Ordinarily, SvelteKit [hydrates](/docs/appendix#hydration) your server-rendered HTML into an interactive page. Some pages don't require JavaScript at all — many blog posts and 'about' pages fall into this category. In these cases you can skip hydration through the `hydrate` export:

```js
/// file: +page.js
export const hydrate = false;
```

> If `hydrate` and `router` are both `false`, SvelteKit will not add any JavaScript to the page at all. If [server-side rendering](/docs/hooks#handle) is disabled in `handle`, `hydrate` must be `true` or no content will be rendered.

### router

SvelteKit includes a [client-side router](/docs/appendix#routing) that intercepts navigations (from the user clicking on links, or interacting with the back/forward buttons) and updates the page contents, rather than letting the browser handle the navigation by reloading.

In certain circumstances you might need to disable [client-side routing](/docs/appendix#routing) through the `router` export:

```js
/// file: +page.js
export const router = false;
```

Note that this will disable client-side routing for any navigation from this page, regardless of whether the router is already active.

### ssr

Normally, SvelteKit renders your page on the server first and sends that HTML to the client where it's hydrated. If you set `ssr` to `false`, it renders an empty 'shell' page instead. This is useful if your page accesses browser-only methods or objects, but in most situations it's not recommended ([see appendix](/docs/appendix#ssr)).

```js
/// file: +page.js
export const ssr = false;
```
11 changes: 0 additions & 11 deletions documentation/docs/15-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ const config = {
adapter: undefined,
alias: {},
appDir: '_app',
browser: {
hydrate: true,
router: true
},
csp: {
mode: 'auto',
directives: {
Expand Down Expand Up @@ -128,13 +124,6 @@ const config = {

The directory relative to `paths.assets` where the built JS and CSS (and imported assets) are served from. (The filenames therein contain content-based hashes, meaning they can be cached indefinitely). Must not start or end with `/`.

### browser

An object containing zero or more of the following `boolean` values:

- `hydrate` — whether to [hydrate](/docs/page-options#hydrate) the server-rendered HTML with a client-side app. (It's rare that you would set this to `false` on an app-wide basis.)
- `router` — enables or disables the client-side [router](/docs/page-options#router) app-wide.

### csp

An object containing zero or more of the following values:
Expand Down
19 changes: 11 additions & 8 deletions documentation/docs/17-seo.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,13 @@ export async function GET() {

#### AMP

An unfortunate reality of modern web development is that it is sometimes necessary to create an [Accelerated Mobile Pages (AMP)](https://amp.dev/) version of your site. In SvelteKit this can be done by enforcing the following [configuration](/docs/configuration) options...
An unfortunate reality of modern web development is that it is sometimes necessary to create an [Accelerated Mobile Pages (AMP)](https://amp.dev/) version of your site. In SvelteKit this can be done by setting the [`inlineStyleThreshold`](/docs/configuration#inlinestylethreshold) option...

```js
/// file: svelte.config.js
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// the combination of these options
// disables JavaScript
browser: {
hydrate: false,
router: false
},

// since <link rel="stylesheet"> isn't
// allowed, inline all styles
inlineStyleThreshold: Infinity
Expand All @@ -106,6 +99,16 @@ const config = {
export default config;
```

...disabling `hydrate` and `router` in your root `+layout.js`/`+layout.server.js`...

```js
/// file: src/routes/+layout.server.js
// the combination of these options
// disables JavaScript
export const hydrate = false;
export const router = false;
```

...and transforming the HTML using `transformPageChunk` along with `transform` imported from `@sveltejs/amp`:

```js
Expand Down
48 changes: 35 additions & 13 deletions packages/adapter-static/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Usage

Install with `npm i -D @sveltejs/adapter-static`, then add the adapter to your `svelte.config.js`:
Install with `npm i -D @sveltejs/adapter-static`, then add the adapter to your `svelte.config.js`...

```js
// svelte.config.js
Expand All @@ -19,16 +19,20 @@ export default {
assets: 'build',
fallback: null,
precompress: false
}),

prerender: {
// This can be false if you're using a fallback (i.e. SPA mode)
default: true
}
})
}
};
```

...and add the [`prerender`](https://kit.svelte.dev/docs/page-options#prerender) option to your root layout:

```js
// src/routes/+layout.js

// This can be false if you're using a fallback (i.e. SPA mode)
export const prerender = true;
```

> ⚠️ You must ensure SvelteKit's [`trailingSlash`](https://kit.svelte.dev/docs/configuration#trailingslash) option is set appropriately for your environment. If your host does not render `/a.html` upon receiving a request for `/a` then you will need to set `trailingSlash: 'always'` to create `/a/index.html` instead.

## Zero-config support
Expand All @@ -44,9 +48,6 @@ export default {
kit: {
- adapter: adapter({...}),
+ adapter: adapter(),

prerender: {
default: true
}
}
};
Expand Down Expand Up @@ -91,11 +92,32 @@ export default {
};
```

When operating in SPA mode, you can omit `config.kit.prerender.default` (or set it to `false`, its default value), and only pages that have the [`prerender`](https://kit.svelte.dev/docs/page-options#prerender) option set will be prerendered at build time.
When operating in SPA mode, you can omit the [`prerender`](https://kit.svelte.dev/docs/page-options#prerender) option from your root layout (or set it to `false`, its default value), and only pages that have the `prerender` option set will be prerendered at build time.

SvelteKit will still crawl your app's entry points looking for prerenderable pages. If `svelte-kit build` fails because of pages that can't be loaded outside the browser, you can set `config.kit.prerender.entries` to `[]` to prevent this from happening. (Setting `config.kit.prerender.enabled` to `false` also has this effect, but would prevent the fallback page from being generated.)

SvelteKit will still crawl your app's entry points looking for prerenderable pages. If `svelte-kit build` fails because of pages that can't be loaded outside the browser, you can set `config.kit.prerender.entries` to `[]` to prevent this from happening. (Setting `config.kit.prerender.enabled` also has this effect, but would prevent the fallback page from being generated.)
During development, SvelteKit will still attempt to server-side render your routes. This means accessing things that are only available in the browser (such as the `window` object) will result in errors, even though this would be valid in the output app. To align the behavior of SvelteKit's dev mode with your SPA, you can [add `export const ssr = false` to your root `+layout`](https://kit.svelte.dev/docs/page-options#ssr).

> ⚠️ During development, SvelteKit will still attempt to server-side render your routes. This means accessing things that are only available in the browser (such as the `window` object) will result in errors, even though this would be valid in the output app. To align the behavior of SvelteKit's dev mode with your SPA, you can [call `resolve()` with a parameter of `{ssr: false}` inside the `handle()` hook](https://kit.svelte.dev/docs/hooks#handle).
If you want to create a simple SPA with no prerendered routes, the necessary config therefore looks like this:

```js
// svelte.config.js
import adapter from '@sveltejs/adapter-static';

export default {
kit: {
adapter: adapter({
fallback: '200.html'
}),
prerender: { entries: [] }
}
};
```

```js
// src/routes/+layout.js
export const ssr = false;
```

## GitHub Pages

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const prerender = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<slot />
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ import adapter from '../../../index.js';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),

prerender: {
default: true
}
adapter: adapter()
}
};

Expand Down
Loading