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

replace externalFetch with handleFetch #6565

Merged
merged 14 commits into from
Sep 5, 2022
46 changes: 40 additions & 6 deletions documentation/docs/06-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Hooks
---

An optional `src/hooks.js` (or `src/hooks.ts`, or `src/hooks/index.js`) file exports three functions, all optional, that run on the server — `handle`, `handleError` and `externalFetch`.
An optional `src/hooks.js` (or `src/hooks.ts`, or `src/hooks/index.js`) file exports three functions, all optional, that run on the server — `handle`, `handleError` and `handleFetch`.

> The location of this file can be [configured](/docs/configuration) as `config.kit.files.hooks`

Expand Down Expand Up @@ -101,15 +101,29 @@ export function handleError({ error, event }) {

> `handleError` is only called for _unexpected_ errors. It is not called for errors created with the [`error`](/docs/modules#sveltejs-kit-error) function imported from `@sveltejs/kit`, as these are _expected_ errors.

### externalFetch
### handleFetch

This function allows you to modify (or replace) a `fetch` request for an external resource that happens inside a `load` function that runs on the server (or during pre-rendering).
This function allows you to modify (or replace) a `fetch` request that happens inside a `load` function that runs on the server (or during pre-rendering).

For example, your `load` function might make a request to a public URL like `https://api.yourapp.com` when the user performs a client-side navigation to the respective page, but during SSR it might make sense to hit the API directly (bypassing whatever proxies and load balancers sit between it and the public internet).
For example, you might need to include custom headers that are added by a proxy that sits in front of your app:

```js
/** @type {import('@sveltejs/kit').ExternalFetch} */
export async function externalFetch(request) {
// @errors: 2345
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
const name = 'x-geolocation-city';
const value = event.request.headers.get(name);
request.headers.set(name, value);

return fetch(request);
}
```

Or your `load` function might make a request to a public URL like `https://api.yourapp.com` when the user performs a client-side navigation to the respective page, but during SSR it might make sense to hit the API directly (bypassing whatever proxies and load balancers sit between it and the public internet).

```js
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ request, fetch }) {
if (request.url.startsWith('https://api.yourapp.com/')) {
// clone the original request, but change the URL
request = new Request(
Expand All @@ -121,3 +135,23 @@ export async function externalFetch(request) {
return fetch(request);
}
```

#### Credentials

For same-origin requests, SvelteKit's `fetch` implementation will forward `cookie` and `authorization` headers unless the `credentials` option is set to `"omit"`.

For cross-origin requests, `cookie` will be included if the request URL belongs to a subdomain of the app — for example if your app is on `my-domain.com`, and your API is on `api.my-domain.com`, cookies will be included in the request.

If your app and your API are on sibling subdomains — `www.my-domain.com` and `api.my-domain.com` for example — then a cookie belonging to a common parent domain like `my-domain.com` will _not_ be included, because SvelteKit has no way to know which domain the cookie belongs to. In these cases you will need to manually include the cookie using `handleFetch`:

```js
// @errors: 2345
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
if (request.url.startsWith('https://api.my-domain.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
}

return fetch(request);
}
```
8 changes: 7 additions & 1 deletion packages/kit/src/exports/vite/build/build_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,16 @@ export class Server {

if (!this.options.hooks) {
const module = await import(${s(hooks)});

// TODO remove this for 1.0
if (module.externalFetch) {
throw new Error('externalFetch has been removed — use handleFetch instead'); // TODO add migration guide
}

this.options.hooks = {
handle: module.handle || (({ event, resolve }) => resolve(event)),
handleError: module.handleError || (({ error }) => console.error(error.stack)),
externalFetch: module.externalFetch || fetch
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request))
};
}
}
Expand Down
8 changes: 7 additions & 1 deletion packages/kit/src/exports/vite/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {

const handle = user_hooks.handle || (({ event, resolve }) => resolve(event));

// TODO remove for 1.0
// @ts-expect-error
if (user_hooks.externalFetch) {
throw new Error('externalFetch has been removed — use handleFetch instead'); // TODO add migration guide
}

/** @type {import('types').Hooks} */
const hooks = {
handle,
Expand All @@ -331,7 +337,7 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
console.error(colors.gray(error.stack));
}
}),
externalFetch: user_hooks.externalFetch || fetch
handleFetch: user_hooks.handleFetch || (({ request, fetch }) => fetch(request))
};

if (/** @type {any} */ (hooks).getContext) {
Expand Down
Loading