Skip to content

Commit

Permalink
[breaking] ssr/hydrate/router/prerender.default are now configurabl…
Browse files Browse the repository at this point in the history
…e in `+page(.server).js` and `+layout(.server).js` (#6197)

* [feat] allow ssr to be configurable in +page.js and +layout.js

* fix tests + docs

* Update documentation/docs/12-page-options.md

Co-authored-by: Rich Harris <richard.a.harris@gmail.com>

* Update documentation/docs/12-page-options.md

Co-authored-by: Rich Harris <richard.a.harris@gmail.com>

* Update documentation/docs/12-page-options.md

Co-authored-by: Rich Harris <richard.a.harris@gmail.com>

* remove browser options in favor of layout/page exports

* remove ssr option from handle, update docs

* test

* update changelog

* make prerender option work in layouts

* adjust test

* update SEO docs

* remove prerender.default

* how did i miss these

* fix bad link

* fix docs

* tweak docs

* i think this order is slightly more logical

* add detail about prerendering server routes

* typo

* put link first

* small tweak

* Update documentation/docs/03-routing.md

* Revert "Update documentation/docs/03-routing.md"

This reverts commit 4dcee25.

Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
  • Loading branch information
3 people authored Aug 30, 2022
1 parent 7f69c52 commit cafdf84
Show file tree
Hide file tree
Showing 50 changed files with 282 additions and 214 deletions.
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

0 comments on commit cafdf84

Please sign in to comment.