@trpc/next
Version:
140 lines (136 loc) • 5.34 kB
JavaScript
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;
;