Skip to content

Commit

Permalink
add resolve opts with ssr parameter
Browse files Browse the repository at this point in the history
Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
  • Loading branch information
JeanJPNM and benmccann committed Jan 9, 2022
1 parent f89fb52 commit af22470
Show file tree
Hide file tree
Showing 10 changed files with 497 additions and 410 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
2 changes: 2 additions & 0 deletions documentation/docs/11-ssr-and-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ You can disable SSR app-wide with the [`ssr` config option](#configuration-ssr),
</script>
```

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 All @@ -142,3 +144,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
23 changes: 20 additions & 3 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({
branch: [],
$session,
options,
page_config: {
hydrate: true,
router: true,
ssr: false
},
status: 200
});
}

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
51 changes: 41 additions & 10 deletions 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,18 @@ 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
}) {
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 All @@ -44,9 +53,12 @@ export async function respond_with_error({ request, options, state, $session, st
})
);

const branch = [
loaded,
/** @type {Loaded} */ (
if (resolve_opts.ssr) {
const default_layout = await options.load_component(options.manifest.layout);
const default_error = await options.load_component(options.manifest.error);

// error pages don't fall through, so we know it's not undefined
const loaded = /** @type {Loaded} */ (
await load_node({
request,
options,
Expand All @@ -56,15 +68,34 @@ export async function respond_with_error({ request, options, state, $session, st
params,
node: default_error,
$session,
stuff: loaded ? loaded.stuff : {},
stuff: {},
prerender_enabled: is_prerender_enabled(options, default_error, state),
is_leaf: false,
is_error: true,
status,
error
is_error: false
})
)
];
);

branch = [
loaded,
/** @type {Loaded} */ (
await load_node({
request,
options,
state,
route: null,
page,
node: default_error,
$session,
stuff: loaded ? loaded.stuff : {},
prerender_enabled: is_prerender_enabled(options, default_error, state),
is_leaf: false,
is_error: true,
status,
error
})
)
];
}

try {
return await render_response({
Expand Down
8 changes: 7 additions & 1 deletion 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 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';
Loading

0 comments on commit af22470

Please sign in to comment.