Skip to content

Commit

Permalink
add an 'ssr' option to 'resolve'
Browse files Browse the repository at this point in the history
  • Loading branch information
benmccann committed Jan 9, 2022
1 parent 71052db commit f6714ae
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/spicy-moose-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Adds an `ssr` parameter to `resolve` to specify whether pages should be loaded and rendered on the server.
29 changes: 26 additions & 3 deletions documentation/docs/04-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,18 @@ export interface Response {
body?: StrictBody;
}

export interface ResolveOpts {
ssr?: boolean;
}

export interface Handle<Locals = Record<string, any>, Body = unknown> {
(input: {
request: Request<Locals, Body>;
resolve(request: Request<Locals, Body>): Response | Promise<Response>;
}): Response | Promise<Response>;
request: ServerRequest<Locals, Body>;
resolve(
request: ServerRequest<Locals, Body>,
opts?: ResolveOpts
): MaybePromise<ServerResponse>;
}): MaybePromise<ServerResponse>;
}
```

Expand All @@ -76,6 +83,22 @@ export async function handle({ request, resolve }) {

You can add call multiple `handle` functions with [the `sequence` helper function](#modules-sveltejs-kit-hooks).

`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` — specifies whether the page will be loaded on the server. Unlike [`config.kit.ssr`](#configuration-ssr) this option does not load pages on the server, thus preventing errors that come from the use of nonexistent variables such as `window` or `document`.


```js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ request, resolve }) {
const response = await resolve(request, {
ssr: !request.path.startsWith('/admin')
});

return response;
}
```

### handleError

If an error is thrown during rendering, this function will be called with the `error` and the `request` 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
15 changes: 11 additions & 4 deletions documentation/docs/11-ssr-and-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@ Disabling [server-side rendering](#appendix-ssr) effectively turns your SvelteKi

> In most situations this is not recommended: see [the discussion in the appendix](#appendix-ssr). Consider whether it's truly appropriate to disable and don't simply disable SSR because you've hit an issue with it.
You can disable SSR app-wide with the [`ssr` config option](#configuration-ssr), or a page-level `ssr` export:
You can disable SSR app-wide with the [`ssr` config option](#configuration-ssr), or on a page-basis passing the `ssr` option to `resolve`:

```html
<script context="module">
export const ssr = false;
</script>
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ request, resolve }) {
const response = await resolve(request, {
ssr: !request.path.startsWith('/admin')
});

return response;
}
```

You can also prevent pages from loading on the server with the [`handle` hook](#hooks-handle).

### router

SvelteKit includes a [client-side router](#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.
Expand Down
20 changes: 17 additions & 3 deletions packages/kit/src/runtime/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export async function respond(incoming, options, state = {}) {
try {
return await options.hooks.handle({
request,
resolve: async (request) => {
resolve: async (request, opts) => {
if (state.prerender && state.prerender.fallback) {
return await render_response({
url: request.url,
Expand All @@ -76,6 +76,7 @@ export async function respond(incoming, options, state = {}) {
});
}

const resolve_opts = get_resolve_opts(opts);
const decoded = decodeURI(request.url.pathname).replace(options.paths.base, '');

for (const route of options.manifest._.routes) {
Expand All @@ -85,7 +86,7 @@ export async function respond(incoming, options, state = {}) {
const response =
route.type === 'endpoint'
? await render_endpoint(request, route, match)
: await render_page(request, route, match, options, state);
: await render_page(request, route, match, options, state, resolve_opts);

if (response) {
// inject ETags for 200 responses
Expand Down Expand Up @@ -125,7 +126,8 @@ export async function respond(incoming, options, state = {}) {
state,
$session,
status: 404,
error: new Error(`Not found: ${request.url.pathname}`)
error: new Error(`Not found: ${request.url.pathname}`),
resolve_opts
});
}
}
Expand Down Expand Up @@ -156,3 +158,15 @@ export async function respond(incoming, options, state = {}) {
}
}
}

/**
* @param {import('types/hooks').ResolveOpts | undefined} opts
* @returns {Required<import('types/hooks').ResolveOpts>}
*/
function get_resolve_opts(opts) {
/** @type {Required<import('types/hooks').ResolveOpts>} */
const defaults = {
ssr: true
};
return Object.assign(defaults, opts);
}
6 changes: 4 additions & 2 deletions packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { respond } from './respond.js';
* @param {RegExpExecArray} match
* @param {import('types/internal').SSRRenderOptions} options
* @param {import('types/internal').SSRRenderState} state
* @param {Required<import('types/hooks').ResolveOpts>} resolve_opts
* @returns {Promise<import('types/hooks').ServerResponse | undefined>}
*/
export async function render_page(request, route, match, options, state) {
export async function render_page(request, route, match, options, state, resolve_opts) {
if (state.initiator === route) {
// infinite request cycle detected
return {
Expand All @@ -29,7 +30,8 @@ export async function render_page(request, route, match, options, state) {
state,
$session,
route,
params
params,
resolve_opts
});

if (response) {
Expand Down
25 changes: 21 additions & 4 deletions packages/kit/src/runtime/server/page/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,30 @@ import { coalesce_to_error } from '../../../utils/error.js';
* $session: any;
* route: import('types/internal').SSRPage;
* params: Record<string, string>;
* resolve_opts: Required<import('types/hooks').ResolveOpts>;
* }} opts
* @returns {Promise<ServerResponse | undefined>}
*/
export async function respond(opts) {
const { request, options, state, $session, route } = opts;
const { request, options, state, $session, route, resolve_opts } = opts;

/** @type {Array<SSRNode | undefined>} */
let nodes;

if (!resolve_opts.ssr) {
return await render_response({
...opts,
branch: [],
page_config: {
hydrate: true,
router: true,
ssr: false
},
status: 200,
url: request.url
});
}

try {
nodes = await Promise.all(
route.a.map((n) => options.manifest._.nodes[n] && options.manifest._.nodes[n]())
Expand All @@ -43,7 +58,8 @@ export async function respond(opts) {
state,
$session,
status: 500,
error
error,
resolve_opts
});
}

Expand Down Expand Up @@ -181,7 +197,8 @@ export async function respond(opts) {
state,
$session,
status,
error
error,
resolve_opts
}),
set_cookie_headers
);
Expand Down Expand Up @@ -231,7 +248,7 @@ export async function respond(opts) {
*/
function get_page_config(leaf, options) {
return {
ssr: 'ssr' in leaf ? !!leaf.ssr : options.ssr,
ssr: true,
router: 'router' in leaf ? !!leaf.router : options.router,
hydrate: 'hydrate' in leaf ? !!leaf.hydrate : options.hydrate
};
Expand Down
19 changes: 18 additions & 1 deletion packages/kit/src/runtime/server/page/respond_with_error.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,26 @@ import { coalesce_to_error } from '../../../utils/error.js';
* $session: any;
* status: number;
* error: Error;
* resolve_opts: Required<import('types/hooks').ResolveOpts>;
* }} opts
*/
export async function respond_with_error({ request, options, state, $session, status, error }) {
export async function respond_with_error({
request,
options,
state,
$session,
status,
error,
resolve_opts
}) {
if (!resolve_opts.ssr) {
return {
status: 500,
headers: {},
body: error.stack
};
}

try {
const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
const default_error = await options.manifest._.nodes[1](); // 1 is always the root error
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/src/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const handle = sequence(
throw new Error('Error in handle');
}

const response = await resolve(request);
const response = await resolve(request, { ssr: !request.url.pathname.startsWith('/no-ssr') });

return {
...response,
Expand Down
6 changes: 1 addition & 5 deletions packages/kit/test/apps/basics/src/routes/no-ssr/index.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
<script context="module">
export const ssr = false;
</script>

<h1>content was rendered</h1>

<a href="/no-ssr/other">other</a>
Expand All @@ -10,4 +6,4 @@
h1 {
color: red;
}
</style>
</style>
13 changes: 11 additions & 2 deletions packages/kit/types/hooks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ export interface GetSession<Locals = Record<string, any>, Body = unknown, Sessio
(request: ServerRequest<Locals, Body>): MaybePromise<Session>;
}

export interface ResolveOpts {
ssr?: boolean;
}

export type RequiredResolveParams = Required<ResolveOpts>;

export interface Handle<Locals = Record<string, any>, Body = unknown> {
(input: {
request: ServerRequest<Locals, Body>;
resolve(request: ServerRequest<Locals, Body>): MaybePromise<ServerResponse>;
resolve(request: ServerRequest<Locals, Body>, opts?: ResolveOpts): MaybePromise<ServerResponse>;
}): MaybePromise<ServerResponse>;
}

Expand All @@ -35,7 +41,10 @@ export interface Handle<Locals = Record<string, any>, Body = unknown> {
export interface InternalHandle<Locals = Record<string, any>, Body = unknown> {
(input: {
request: ServerRequest<Locals, Body>;
resolve(request: ServerRequest<Locals, Body>): MaybePromise<ServerResponse | undefined>;
resolve(
request: ServerRequest<Locals, Body>,
opts?: ResolveOpts
): MaybePromise<ServerResponse | undefined>;
}): MaybePromise<ServerResponse | undefined>;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export {
Handle,
HandleError,
ServerRequest as Request,
ServerResponse as Response
ServerResponse as Response,
ResolveOpts
} from './hooks';

0 comments on commit f6714ae

Please sign in to comment.