UNPKG

@trpc/next

Version:

The tRPC Next.js library

140 lines (136 loc) 5.34 kB
'use strict'; var reactQuery = require('@tanstack/react-query'); var client = require('@trpc/client'); var unstableInternals = require('@trpc/client/unstable-internals'); var shared = require('@trpc/react-query/shared'); var React = require('react'); function transformQueryOrMutationCacheErrors(result) { const error = result.state.error; if (error instanceof Error && error.name === 'TRPCClientError') { const newError = { message: error.message, data: error.data, shape: error.shape }; return { ...result, state: { ...result.state, error: newError } }; } return result; } const ssrPrepass = (opts)=>{ const { parent, WithTRPC, AppOrPage } = opts; const transformer = unstableInternals.getTransformer(parent.transformer); WithTRPC.getInitialProps = async (appOrPageCtx)=>{ const shouldSsr = async ()=>{ if (typeof window !== 'undefined') { return false; } if (typeof parent.ssr === 'function') { try { return await parent.ssr({ ctx: appOrPageCtx.ctx }); } catch { return false; } } return parent.ssr; }; const ssrEnabled = await shouldSsr(); const AppTree = appOrPageCtx.AppTree; // Determine if we are wrapping an App component or a Page component. const isApp = !!appOrPageCtx.Component; const ctx = isApp ? appOrPageCtx.ctx : appOrPageCtx; // Run the wrapped component's getInitialProps function. let pageProps = {}; if (AppOrPage.getInitialProps) { const originalProps = await AppOrPage.getInitialProps(appOrPageCtx); const originalPageProps = isApp ? originalProps.pageProps ?? {} : originalProps; pageProps = { ...originalPageProps, ...pageProps }; } const getAppTreeProps = (props)=>isApp ? { pageProps: props } : props; if (typeof window !== 'undefined' || !ssrEnabled) { return getAppTreeProps(pageProps); } const config = parent.config({ ctx }); const trpcClient = client.createTRPCUntypedClient(config); const queryClient = shared.getQueryClient(config); const trpcProp = { config, trpcClient, queryClient, ssrState: 'prepass', ssrContext: ctx }; const prepassProps = { pageProps, trpc: trpcProp }; const reactDomServer = await import('react-dom/server'); // Run the prepass step on AppTree. This will run all trpc queries on the server. // multiple prepass ensures that we can do batching on the server while(true){ // render full tree reactDomServer.renderToString(React.createElement(AppTree, prepassProps)); if (!queryClient.isFetching()) { break; } // wait until the query cache has settled it's promises await new Promise((resolve)=>{ const unsub = queryClient.getQueryCache().subscribe((event)=>{ if (event?.query.getObserversCount() === 0) { resolve(); unsub(); } }); }); } const dehydratedCache = reactQuery.dehydrate(queryClient, { shouldDehydrateQuery (query) { // filter out queries that are marked as trpc: { ssr: false } or are not enabled, but make sure errors are dehydrated const isExcludedFromSSr = query.state.fetchStatus === 'idle' && query.state.status === 'pending'; return !isExcludedFromSSr; } }); // since error instances can't be serialized, let's make them into `TRPCClientErrorLike`-objects const dehydratedCacheWithErrors = { ...dehydratedCache, queries: dehydratedCache.queries.map(transformQueryOrMutationCacheErrors), mutations: dehydratedCache.mutations.map(transformQueryOrMutationCacheErrors) }; // dehydrate query client's state and add it to the props pageProps['trpcState'] = transformer.input.serialize(dehydratedCacheWithErrors); const appTreeProps = getAppTreeProps(pageProps); const meta = parent.responseMeta?.({ ctx, clientErrors: [ ...dehydratedCache.queries, ...dehydratedCache.mutations ].map((v)=>v.state.error).flatMap((err)=>err instanceof Error && err.name === 'TRPCClientError' ? [ err ] : []) }) ?? {}; for (const [key, value] of Object.entries(meta.headers ?? {})){ if (typeof value === 'string') { ctx.res?.setHeader(key, value); } } if (meta.status && ctx.res) { ctx.res.statusCode = meta.status; } return appTreeProps; }; }; exports.ssrPrepass = ssrPrepass;