kit: Setup each page - init hook, `export const blocking`, etc.

Describe the problem

For i18n or similar subsystems, we should make initialization before any language things are needed, and for such initialization, we usually use a session (user language) and information from the HTTP request. Before the new routing system was introduced, we were using the root __layout.svelte to do it, it’s working well for SSR and CSR time. I should mention that what i18n should be inited on the server side and init on the client side as well (or state should be send as is).

After migration to new routing, we lose one “transitional” place to init such thing because +layout.js can be called after +page.js, and you must do await parent() in each page to avoid such race condition.

For server-side only init, we have a good place it’s handle in hooks.js with event.locals it’s an excellent way to init server only things like DB connection, but for things what should be a transition from SSR to CSR we have no place.

I also have a similar issue for i18n implementation project what I use https://github.com/cibernox/svelte-intl-precompile/issues/55

Describe the proposed solution

Maybe it’s possible to do it more straightforwardly, but at least we need a global hook for what will be called on SSR and on CSR, after +layout.server.js but before +layout.js and +page.js.

Alternatives considered

No response

Importance

would make my life easier

Additional Information

No response

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 6
  • Comments: 34 (30 by maintainers)

Commits related to this issue

Most upvoted comments

Here’s what i’ve been experimenting with today:

lang.ts

import type { Handle, RequestEvent } from '@sveltejs/kit';
import {
	init,
	register,
	waitLocale,
	getLocaleFromAcceptLanguageHeader,
	getLocaleFromNavigator
} from 'svelte-intl-precompile';
register('en', () => import('$locales/en'));
register('de', () => import('$locales/de'));

const DEFAULT_LOCALE = 'en';

// add this hook to your hooks.server.ts sequence 
// and update app.html to use `<html lang="%lang%">`
export const setLocale: Handle = async ({ event, resolve }) => {
	const lang = await loadLocale(event);
	return resolve(event, {
		transformPageChunk: ({ html }) => html.replace('%lang%', lang)
	});
};

// call this function with await in hooks.client.ts
export async function loadLocale(event?) {
	let locale = event ? getSSRLocale(event) : getClientLocale();
	init({ initialLocale: locale, fallbackLocale: DEFAULT_LOCALE });
	await waitLocale(locale);
	return locale;
}

function getSSRLocale(event: RequestEvent) {
        // prefer stored user locale, fall back to accept header and default
	return (
		event.locals.user?.locale ||
		getLocaleFromAcceptLanguageHeader(event.request.headers.get('Accept-Language')) ||
		DEFAULT_LOCALE
	);
}

function getClientLocale() {
	// html lang attr is set by SSR hook, so we just reuse that
	// otherwise fall back to navigator or default
	return document?.documentElement.lang || getLocaleFromNavigator() || DEFAULT_LOCALE;
}

hooks.client.ts

import { loadLocale } from './lang';
await loadLocale();

hooks.server.ts

import { setLocale } from './lang';
export const handle: Handle = sequence(/*auth etc */, setLocale);

Is this a valid approach?

My proposal would be an exported init or initialize (can’t think of a better name) function in hooks.client.js that gets called (and awaited) right after the init function in https://github.com/sveltejs/kit/blob/177a5a9f8219f3f9633d8f8dc879f8472f74d6a2/packages/kit/src/runtime/client/start.js#L28

Maybe the returned value could be the equivalent of locals in the client-side load functions or merged with data.

If I understand you correctly, it could work today for you, too, but you’d have to do await parent() in each layout/page, which is cumbersome. I had this concern, too. My idea was to introduce export const blocks = <boolean> to +layout.js (and possibly to layout.server.js) so you have to write it in one place only for situations like this.

Another thing coming up from a discussion: When doing this in the root layout and the setup depends on things that are causing the load function to rerun, the setup code might be rerun when you don’t want that. #6294 could also help with that.