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

ssr/router/hydrate options (SPA mode! 0kb JS pages!) #713

Merged
merged 10 commits into from
Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from 8 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
31 changes: 0 additions & 31 deletions documentation/docs/11-prerendering.md

This file was deleted.

77 changes: 77 additions & 0 deletions documentation/docs/11-ssr-and-javascript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: SSR and JavaScript
---

By default, SvelteKit will server-render pages on-demand, then **hydrate** the rendered HTML in the client with an interactive Svelte app while initialising a **router** that takes over subsequent navigations.
Conduitry marked this conversation as resolved.
Show resolved Hide resolved

You can control each of these on a per-app or per-page basis. Note that each of the per-page settings use [`context="module"`](https://svelte.dev/docs#script_context_module), and only apply to page components, _not_ [layout](#layouts) components.

If both are specified, per-page settings override per-app settings in case of conflicts. Each setting can be controlled independently, but `ssr` and `hydrate` cannot both be `false` since that would result in nothing being rendered at all.

### ssr

Disabling server-side rendering effectively turns your SvelteKit app into a **single-page app** or SPA.

> In most situations this is not recommended: it harms SEO, tends to slow down perceived performance, and makes your app inaccessible to users if JavaScript fails or is disabled (which happens [more often than you probably think](https://kryogenix.org/code/browser/everyonehasjs.html)). Sometimes it's appropriate or even necessary, but consider alternatives before disabling SSR.

You can disable SSR app-wide with the [`ssr` config option](#configuration-ssr), or a page-level `ssr` export:

```html
<script context="module">
export const ssr = false;
</script>
```

### router

SvelteKit includes a client-side router 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 this behaviour with the app-wide [`router` config option](#configuration-router) or the page-level `router` export:
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved

```html
<script context="module">
export const router = false;
</script>
```

### hydrate

Ordinarily, SvelteKit 'hydrates' 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 [`hydrate` config option](#configuration-hydrate) or the page-level `hydrate` export:

```html
<script context="module">
export const hydrate = false;
</script>
```

> If `hydrate` and `router` are both `false`, SvelteKit will not add any JavaScript to the page at all.

### prerender

It's likely that at least some pages of your app can be represented as a simple HTML file, since they contain no dynamic or user-specific data. These pages can be _prerendered_ by your [adapter](#adapters).

If your entire app is suitable for prerendering, you could use [`adapter-static`](https://github.com/sveltejs/kit/tree/master/packages/adapter-static), which will generate HTML files for every page, plus additional files that are requested by `load` functions in those pages.

More likely, you'll only want to prerender specific pages in your app. You'll need to annotate these pages:

```html
<script context="module">
export const prerender = true;
</script>
```

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 `pages` option in the [prerender configuration](#configuration-prerender).

#### When not to prerender

The basic rule is this: for a page to be prerenderable, any two users hitting the same page of your app must get the same content from the server. In other words, any app that involves user sessions or authentication is _not_ a candidate for the static adapter.
Conduitry marked this conversation as resolved.
Show resolved Hide resolved

Note that you can still prerender pages that load data based on the page's parameters, like our `src/routes/blog/[slug].svelte` example from earlier. The static adapter will intercept requests made inside `load`, so the data served from `src/routes/blog/[slug].json.js` will also be captured.

#### Route conflicts

Because prerendering writes to the filesystem, it isn't possible to have two endpoints that would cause a directory and a file to have the same name. For example, `src/routes/foo/index.js` and `src/routes/foo/bar.js` would try to create `foo` and `foo/bar`, which is impossible.

For that reason among others, it's recommended that you always include a file extension — `src/routes/foo/index.json.js` and `src/routes/foo/bar.json.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`.
15 changes: 15 additions & 0 deletions documentation/docs/13-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
},
host: null,
hostHeader: null,
hydrate: true,
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved
paths: {
assets: '',
base: ''
Expand All @@ -37,6 +38,8 @@ module.exports = {
force: false,
pages: ['*']
},
router: true,
ssr: true,
target: null,
vite: () => ({})
},
Expand Down Expand Up @@ -88,6 +91,10 @@ module.exports = {

**You should only do this if you trust the reverse proxy**, which is why it isn't the default.

#### hydrate

Whether to [hydrate](#ssr-and-javascript-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.)

#### paths

An object containing zero or more of the following `string` values:
Expand All @@ -104,6 +111,14 @@ See [Prerendering](#prerendering). An object containing zero or more of the foll
- `force` — if `true`, a page that fails to render will _not_ cause the entire build to fail
- `pages` — an array of pages to prerender, or start crawling from (if `crawl: true`). The `*` string includes all non-dynamic routes (i.e. pages with no `[parameters]` )

#### router

Enables or disables the client-side [router](#ssr-and-javascript-router) app-wide.

#### ssr

Enables or disables [server-side rendering](#ssr-and-javascript-ssr) app-wide.

#### target

Specifies an element to mount the app to. It must be a DOM selector that identifies an element that exists in your template file. If unspecified, the app will be mounted to `document.body`.
Expand Down
1 change: 1 addition & 0 deletions examples/hn.svelte.dev/src/routes/about.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script context="module">
export const hydrate = false;
export const prerender = true;
</script>

Expand Down
2 changes: 2 additions & 0 deletions examples/hn.svelte.dev/src/routes/user/[name].svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script context="module">
export const hydrate = false;

export async function load({ page, fetch }) {
const res = await fetch(`https://api.hnpwa.com/v0/user/${page.params.name}.json`);
const user = await res.json();
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/src/core/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,10 @@ async function build_server(
get_component_path: id => ${s(`${config.kit.paths.assets}/${config.kit.appDir}/`)} + client_component_lookup[id],
get_stack: error => error.stack,
get_static_file,
get_amp_css: dep => amp_css_lookup[dep]
get_amp_css: dep => amp_css_lookup[dep],
ssr: ${s(config.kit.ssr)},
router: ${s(config.kit.router)},
hydrate: ${s(config.kit.hydrate)}
});
}
`
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/src/core/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,10 @@ class Watcher extends EventEmitter {
},
get_static_file: (file) =>
fs.readFileSync(path.join(this.config.kit.files.assets, file)),
get_amp_css: (url) => '' // TODO: implement this
get_amp_css: (url) => '', // TODO: implement this
ssr: this.config.kit.ssr,
router: this.config.kit.router,
hydrate: this.config.kit.hydrate
}
);

Expand Down
6 changes: 6 additions & 0 deletions packages/kit/src/core/load_config/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ test('fills in defaults', () => {
},
host: null,
hostHeader: null,
hydrate: true,
paths: {
base: '',
assets: '/.'
Expand All @@ -35,6 +36,8 @@ test('fills in defaults', () => {
force: false,
pages: ['*']
},
router: true,
ssr: true,
target: null
},
preprocess: null
Expand Down Expand Up @@ -102,6 +105,7 @@ test('fills in partial blanks', () => {
},
host: null,
hostHeader: null,
hydrate: true,
paths: {
base: '',
assets: '/.'
Expand All @@ -112,6 +116,8 @@ test('fills in partial blanks', () => {
force: false,
pages: ['*']
},
router: true,
ssr: true,
target: null
},
preprocess: null
Expand Down
6 changes: 6 additions & 0 deletions packages/kit/src/core/load_config/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const options = {

hostHeader: expect_string(null),

hydrate: expect_boolean(true),

paths: {
type: 'branch',
children: {
Expand Down Expand Up @@ -112,6 +114,10 @@ const options = {
}
},

router: expect_boolean(true),

ssr: expect_boolean(true),

target: expect_string(null),

vite: {
Expand Down
3 changes: 3 additions & 0 deletions packages/kit/src/core/load_config/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ suite('load default config', async () => {
},
host: null,
hostHeader: null,
hydrate: true,
paths: { base: '', assets: '/.' },
prerender: { crawl: true, enabled: true, force: false, pages: ['*'] },
router: true,
ssr: true,
target: null
},
preprocess: null
Expand Down
Loading