kit
kit copied to clipboard
There is no way of transfering functions and/or objects with associated methods to client code during SSR in SvelteKit, but it is present in Svelte with Server-side component API
Describe the problem
i.e. I want to log errors during SSR to Sentry while having access to RequestEvent
parts.
Describe the proposed solution
Use Svelte Server-side component API in SvelteKit via passing context to client code during SSR.
Alternatives considered
- Ignoring errors during SSR as same errors will be logged via client side logging. Sounds... sad.
- Passing all required options down to client-side code to create server-side logger only during SSR. Can be quite cumbersome.
Importance
would make my life easier
Additional Information
Proposed solution has open PR – https://github.com/sveltejs/kit/pull/10064.
Additional info from there:
@secondfry:
Sometimes you need a function defined only in server context – i.e. to log errors inside of SSR. This PR allows one to define context in RequestEvent
as follows:
hooks.server.ts
const handle: Handle = async ({ event, resolve }) => {
const logger = new Logger();
event.context = new Map();
event.context.set('logger', logger);
return await resolve(event);
});
some +page.svelte
import { browser } from '$app/environment';
const logger = browser ? new ClientLogger() : getContext('logger');
logger.info('yes');
@benmccann:
const logger = browser ? new ClientLogger() : getContext('logger');
I feel like there's not enough context here to understand this PR and there should probably be an associated issue. Why can you do new ClientLogger()
but can't do new ServerLogger()
?
@secondfry: @benmccann in case of very simple logging requirements, yes, maybe one can do that. But the moment you will need to transfer such logs to i.e. Sentry, with request context, timings, etc., you will question yourself: why can't you just pass already initialized logger from server side?
Expanding on the example you would have something like this:
hooks.server.ts
const initLogger = async ({ event, resolve }) => {
event.locals.logger = new QuiteComplicatedServerLogger({ endpoint, format, ... });
return await resolve(event);
};
const quiteComplicatedThingWhichRequiresLoggerA = async ({ event, resolve }) => {
try { await explode(); } catch (cause) { event.locals.logger(new Error('Unexpected explosion', { cause })); }
return await resolve(event);
};
const quiteComplicatedThingWhichRequiresLoggerB = ...;
const quiteComplicatedThingWhichRequiresLoggerC = ...;
const handle: Handle = sequence(
initLogger,
quiteComplicatedThingWhichRequiresLoggerA,
quiteComplicatedThingWhichRequiresLoggerB,
quiteComplicatedThingWhichRequiresLoggerC,
async ({ event, resolve }) => {
event.context = new Map();
event.context.set('logger', event.locals.logger);
return await resolve(event);
}
);
Otherwise one would have to pass all options of QuiteComplicatedServerLogger
to svelte code.
So as Svelte has Server-side component API which allows passing context it seemed to us as the most logical choice. If there are other ways to pass functions and/or objects with associated methods into client code during SSR, I'm would gladly learn that.
I think this is reasonably simple already:
- Create a module that returns the logger
- Attach the logger to a context in
+layout.svelte
For example:
// $lib/logger
const loggerContextKey = new Symbol('logger');
export function createLogger(config) {
return browser ? new ClientLogger(config) : new ServerLogger(config);
}
export function createLoggerContext(logger) {
setContext(loggerContextKey, logger);
}
export function getLogger() {
return getContext(loggerContextKey);
}
<script>
// routes/+layout.svelte
import { createLogger, createLoggerContext } from `$lib/logger`;
createLoggerContext(createLogger);
</script>
<slot />
You'll have to adjust the module depending on whether you need your logger scoped to the request (you can simplify if you can share an instance between all requests on the server), but that should get you pretty much all the way there.
(disclaimer: typed on phone)
Agree with @tcc-sejohnson that should be possible to do with existing methods. Additionally, if you need some initial value passed to the logger context you could always return that from your load function.
/// file: +layout.js
import { browser } from '$app/environment';
export function load() {
return {
stuff: 'foo',
loggerStuff: browser ? undefined : something
}
}
<!-- file: +layout.svelte -->
<script>
export let data;
createLoggerContext(logger, data.loggerStuff);
</script>
Is there a use case where this wouldn't work which I'm missing?
Setting/getting logger via context is too late, such logger won't be available in hooks since contexts are only creatable from Svelte components.