perf: Remove ssrInit from layout and event-types (#20368)
* perf: Remove ssrInit from layout and event-types * remove ssg file since not used * refator trpc usage in app router * fix --------- Co-authored-by: hbjORbj <sldisek783@gmail.com>
This commit is contained in:
co-authored by
hbjORbj
parent
2ab4e8b40a
commit
be883aac02
@@ -8,8 +8,6 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
import EventTypes, { EventTypesCTA } from "~/event-types/views/event-types-listing-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
@@ -26,7 +24,6 @@ const Page = async ({ params, searchParams }: PageProps) => {
|
||||
redirect("/auth/login");
|
||||
}
|
||||
|
||||
await ssrInit(context);
|
||||
const t = await getTranslate();
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,8 +4,6 @@ import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
import Logout from "~/auth/logout-view";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
@@ -19,7 +17,6 @@ const Page = async ({ params, searchParams }: PageProps) => {
|
||||
// cookie will be cleared in `/apps/web/middleware.ts`
|
||||
const h = await headers();
|
||||
const context = buildLegacyCtx(h, await cookies(), await params, await searchParams);
|
||||
await ssrInit(context);
|
||||
|
||||
return <Logout query={context.query} />;
|
||||
};
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { createHydrateClient } from "app/_trpc/createHydrateClient";
|
||||
import superjson from "superjson";
|
||||
|
||||
export const HydrateClient = createHydrateClient({
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { type DehydratedState, HydrationBoundary } from "@tanstack/react-query";
|
||||
import { useMemo } from "react";
|
||||
|
||||
type DataTransformer = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
serialize: (data: any) => any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
deserialize: (data: any) => any;
|
||||
};
|
||||
|
||||
export function createHydrateClient(opts: { transformer: DataTransformer }) {
|
||||
return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) {
|
||||
const { state, children } = props;
|
||||
|
||||
const transformedState: DehydratedState = useMemo(() => {
|
||||
if (opts.transformer) {
|
||||
return opts.transformer.deserialize(state);
|
||||
}
|
||||
return state;
|
||||
}, [state]);
|
||||
|
||||
return <HydrationBoundary state={transformedState}>{children}</HydrationBoundary>;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { TRPCClientError } from "@trpc/client";
|
||||
|
||||
const MAX_QUERY_RETRIES = 3;
|
||||
|
||||
const isTRPCClientError = (cause: unknown): cause is TRPCClientError<any> => {
|
||||
return cause instanceof TRPCClientError;
|
||||
};
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
// these configurations are copied from "packages/trpc/react/trpc.ts"
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
/**
|
||||
* 1s should be enough to just keep identical query waterfalls low
|
||||
* @example if one page components uses a query that is also used further down the tree
|
||||
*/
|
||||
staleTime: 1000,
|
||||
/**
|
||||
* Retry `useQuery()` calls depending on this function
|
||||
*/
|
||||
retry(failureCount, error) {
|
||||
if (isTRPCClientError(error) && error.data) {
|
||||
const { code } = error.data;
|
||||
if (code === "BAD_REQUEST" || code === "FORBIDDEN" || code === "UNAUTHORIZED") {
|
||||
// if input data is wrong or you're not authorized there's no point retrying a query
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return failureCount < MAX_QUERY_RETRIES;
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
import type { TRPCContext } from "@calcom/trpc/server/createContext";
|
||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
||||
|
||||
export const getServerCaller = (ctx: TRPCContext) => appRouter.createCaller(ctx);
|
||||
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import superjson from "superjson";
|
||||
|
||||
import { ENDPOINTS } from "@calcom/trpc/react/shared";
|
||||
|
||||
import { httpBatchLink, httpLink, loggerLink, splitLink } from "@trpc/client";
|
||||
|
||||
import { trpc } from "./trpc";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const resolveEndpoint = (links: any) => {
|
||||
// TODO: Update our trpc routes so they are more clear.
|
||||
// This function parses paths like the following and maps them
|
||||
// to the correct API endpoints.
|
||||
// - viewer.me - 2 segment paths like this are for logged in requests
|
||||
// - viewer.public.i18n - 3 segments paths can be public or authed
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (ctx: any) => {
|
||||
const parts = ctx.op.path.split(".");
|
||||
let endpoint;
|
||||
let path = "";
|
||||
if (parts.length == 2) {
|
||||
endpoint = parts[0] as keyof typeof links;
|
||||
path = parts[1];
|
||||
} else {
|
||||
endpoint = parts[1] as keyof typeof links;
|
||||
path = parts.splice(2, parts.length - 2).join(".");
|
||||
}
|
||||
return links[endpoint]({ ...ctx, op: { ...ctx.op, path } });
|
||||
};
|
||||
};
|
||||
|
||||
const url =
|
||||
typeof window !== "undefined"
|
||||
? "/api/trpc"
|
||||
: process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}/api/trpc`
|
||||
: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`;
|
||||
|
||||
export const trpcClient = trpc.createClient({
|
||||
links: [
|
||||
// adds pretty logs to your console in development and logs errors in production
|
||||
loggerLink({
|
||||
enabled: (opts) =>
|
||||
(typeof process.env.NEXT_PUBLIC_LOGGER_LEVEL === "number" &&
|
||||
process.env.NEXT_PUBLIC_LOGGER_LEVEL >= 0) ||
|
||||
(opts.direction === "down" && opts.result instanceof Error),
|
||||
}),
|
||||
splitLink({
|
||||
// check for context property `skipBatch`
|
||||
condition: (op) => !!op.context.skipBatch,
|
||||
// when condition is true, use normal request
|
||||
true: (runtime) => {
|
||||
const links = Object.fromEntries(
|
||||
ENDPOINTS.map((endpoint) => [
|
||||
endpoint,
|
||||
httpLink({
|
||||
url: `${url}/${endpoint}`,
|
||||
})(runtime),
|
||||
])
|
||||
);
|
||||
return resolveEndpoint(links);
|
||||
},
|
||||
// when condition is false, use batch request
|
||||
false: (runtime) => {
|
||||
const links = Object.fromEntries(
|
||||
ENDPOINTS.map((endpoint) => [
|
||||
endpoint,
|
||||
httpBatchLink({
|
||||
url: `${url}/${endpoint}`,
|
||||
})(runtime),
|
||||
])
|
||||
);
|
||||
return resolveEndpoint(links);
|
||||
},
|
||||
}),
|
||||
],
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -1,137 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { type DehydratedState, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { HydrateClient } from "app/_trpc/HydrateClient";
|
||||
import { trpc } from "app/_trpc/client";
|
||||
import { useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINTS } from "@calcom/trpc/react/shared";
|
||||
import type { AppRouter } from "@calcom/trpc/types/server/routers/_app";
|
||||
|
||||
import { httpBatchLink, httpLink, loggerLink, splitLink, TRPCClientError } from "@trpc/client";
|
||||
|
||||
export type Endpoint = (typeof ENDPOINTS)[number];
|
||||
|
||||
const MAX_QUERY_RETRIES = 3;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const resolveEndpoint = (links: any) => {
|
||||
// TODO: Update our trpc routes so they are more clear.
|
||||
// This function parses paths like the following and maps them
|
||||
// to the correct API endpoints.
|
||||
// - viewer.me - 2 segment paths like this are for logged in requests
|
||||
// - viewer.public.i18n - 3 segments paths can be public or authed
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (ctx: any) => {
|
||||
const parts = ctx.op.path.split(".");
|
||||
let endpoint;
|
||||
let path = "";
|
||||
if (parts.length == 2) {
|
||||
endpoint = parts[0] as keyof typeof links;
|
||||
path = parts[1];
|
||||
} else {
|
||||
endpoint = parts[1] as keyof typeof links;
|
||||
path = parts.splice(2, parts.length - 2).join(".");
|
||||
}
|
||||
return links[endpoint]({ ...ctx, op: { ...ctx.op, path } });
|
||||
};
|
||||
};
|
||||
|
||||
const isTRPCClientError = (cause: unknown): cause is TRPCClientError<AppRouter> => {
|
||||
return cause instanceof TRPCClientError;
|
||||
};
|
||||
import { queryClient } from "./query-client";
|
||||
import { trpc } from "./trpc";
|
||||
import { trpcClient } from "./trpc-client";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
dehydratedState: DehydratedState;
|
||||
};
|
||||
|
||||
export const TrpcProvider = ({ children, dehydratedState }: Props) => {
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
// these configurations are copied from "packages/trpc/react/trpc.ts"
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
/**
|
||||
* 1s should be enough to just keep identical query waterfalls low
|
||||
* @example if one page components uses a query that is also used further down the tree
|
||||
*/
|
||||
staleTime: 1000,
|
||||
/**
|
||||
* Retry `useQuery()` calls depending on this function
|
||||
*/
|
||||
retry(failureCount, error) {
|
||||
if (isTRPCClientError(error) && error.data) {
|
||||
const { code } = error.data;
|
||||
if (code === "BAD_REQUEST" || code === "FORBIDDEN" || code === "UNAUTHORIZED") {
|
||||
// if input data is wrong or you're not authorized there's no point retrying a query
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return failureCount < MAX_QUERY_RETRIES;
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const url =
|
||||
typeof window !== "undefined"
|
||||
? "/api/trpc"
|
||||
: process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}/api/trpc`
|
||||
: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`;
|
||||
|
||||
const [trpcClient] = useState(() =>
|
||||
trpc.createClient({
|
||||
links: [
|
||||
// adds pretty logs to your console in development and logs errors in production
|
||||
loggerLink({
|
||||
enabled: (opts) =>
|
||||
(typeof process.env.NEXT_PUBLIC_LOGGER_LEVEL === "number" &&
|
||||
process.env.NEXT_PUBLIC_LOGGER_LEVEL >= 0) ||
|
||||
(opts.direction === "down" && opts.result instanceof Error),
|
||||
}),
|
||||
splitLink({
|
||||
// check for context property `skipBatch`
|
||||
condition: (op) => !!op.context.skipBatch,
|
||||
// when condition is true, use normal request
|
||||
true: (runtime) => {
|
||||
const links = Object.fromEntries(
|
||||
ENDPOINTS.map((endpoint) => [
|
||||
endpoint,
|
||||
httpLink({
|
||||
url: `${url}/${endpoint}`,
|
||||
})(runtime),
|
||||
])
|
||||
);
|
||||
return resolveEndpoint(links);
|
||||
},
|
||||
// when condition is false, use batch request
|
||||
false: (runtime) => {
|
||||
const links = Object.fromEntries(
|
||||
ENDPOINTS.map((endpoint) => [
|
||||
endpoint,
|
||||
httpBatchLink({
|
||||
url: `${url}/${endpoint}`,
|
||||
})(runtime),
|
||||
])
|
||||
);
|
||||
return resolveEndpoint(links);
|
||||
},
|
||||
}),
|
||||
],
|
||||
transformer: superjson,
|
||||
})
|
||||
);
|
||||
|
||||
export const TrpcProvider = ({ children }: Props) => {
|
||||
return (
|
||||
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<HydrateClient state={dehydratedState}>{children}</HydrateClient>
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
</trpc.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { AppRouter } from "@calcom/trpc/types/server/routers/_app";
|
||||
|
||||
import { createTRPCReact } from "@trpc/react-query";
|
||||
@@ -8,10 +8,6 @@ import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { IconSprites } from "@calcom/ui/components/icon";
|
||||
import { NotificationSoundHandler } from "@calcom/web/components/notification-sound-handler";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
import "../styles/globals.css";
|
||||
import { SpeculationRules } from "./SpeculationRules";
|
||||
import { Providers } from "./providers";
|
||||
@@ -112,7 +108,6 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
? getFallbackProps()
|
||||
: await getInitialProps(fullUrl);
|
||||
|
||||
const ssr = await ssrInit(buildLegacyCtx(h, await cookies(), {}, {}));
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
@@ -178,7 +173,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
"/insights",
|
||||
]}
|
||||
/>
|
||||
<Providers dehydratedState={ssr.dehydrate()}>{children}</Providers>
|
||||
<Providers>{children}</Providers>
|
||||
{!isEmbed && <NotificationSoundHandler />}
|
||||
<NotificationSoundHandler />
|
||||
</body>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { type DehydratedState } from "@tanstack/react-query";
|
||||
import { TrpcProvider } from "app/_trpc/trpc-provider";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import CacheProvider from "react-inlinesvg/provider";
|
||||
@@ -12,14 +11,13 @@ import PlainChat from "@lib/plain/dynamicProvider";
|
||||
|
||||
type ProvidersProps = {
|
||||
children: React.ReactNode;
|
||||
dehydratedState: DehydratedState;
|
||||
};
|
||||
export function Providers({ children, dehydratedState }: ProvidersProps) {
|
||||
export function Providers({ children }: ProvidersProps) {
|
||||
const isBookingPage = useIsBookingPage();
|
||||
|
||||
return (
|
||||
<SessionProvider>
|
||||
<TrpcProvider dehydratedState={dehydratedState}>
|
||||
<TrpcProvider>
|
||||
{!isBookingPage ? <PlainChat /> : null}
|
||||
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
|
||||
<CacheProvider>
|
||||
|
||||
@@ -3,13 +3,9 @@ import type { GetServerSidePropsContext } from "next";
|
||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const { req } = context;
|
||||
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
let appStore;
|
||||
@@ -29,7 +25,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
return {
|
||||
props: {
|
||||
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
||||
trpcState: ssr.dehydrate(),
|
||||
trpcState: undefined,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,13 +5,9 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
import type { AppCategories } from "@calcom/prisma/enums";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const { req } = context;
|
||||
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
let appStore, userAdminTeamsIds: number[];
|
||||
@@ -46,7 +42,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
}),
|
||||
appStore,
|
||||
userAdminTeams: userAdminTeamsIds,
|
||||
trpcState: ssr.dehydrate(),
|
||||
trpcState: undefined,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { GetStaticPropsContext } from "next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import superjson from "superjson";
|
||||
|
||||
import { CALCOM_VERSION } from "@calcom/lib/constants";
|
||||
import prisma, { readonlyPrisma } from "@calcom/prisma";
|
||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
||||
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { i18n } = require("@calcom/config/next-i18next.config");
|
||||
|
||||
/**
|
||||
* Initialize static site rendering tRPC helpers.
|
||||
* Provides a method to prefetch tRPC-queries in a `getStaticProps`-function.
|
||||
* Automatically prefetches i18n based on the passed in `context`-object to prevent i18n-flickering.
|
||||
* Make sure to `return { props: { trpcState: ssr.dehydrate() } }` at the end.
|
||||
*/
|
||||
export async function ssgInit<TParams extends { locale?: string }>(opts: GetStaticPropsContext<TParams>) {
|
||||
const requestedLocale = opts.params?.locale || opts.locale || i18n.defaultLocale;
|
||||
const isSupportedLocale = i18n.locales.includes(requestedLocale);
|
||||
if (!isSupportedLocale) {
|
||||
console.warn(`Requested unsupported locale "${requestedLocale}"`);
|
||||
}
|
||||
const locale = isSupportedLocale ? requestedLocale : i18n.defaultLocale;
|
||||
|
||||
const _i18n = await serverSideTranslations(locale, ["common"]);
|
||||
|
||||
const ssg = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
transformer: superjson,
|
||||
ctx: {
|
||||
prisma,
|
||||
insightsDb: readonlyPrisma,
|
||||
session: null,
|
||||
locale,
|
||||
i18n: _i18n,
|
||||
},
|
||||
});
|
||||
|
||||
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
|
||||
// we can set query data directly to the queryClient
|
||||
const queryKey = [
|
||||
["viewer", "public", "i18n"],
|
||||
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
|
||||
];
|
||||
|
||||
ssg.queryClient.setQueryData(queryKey, { i18n: _i18n });
|
||||
|
||||
return ssg;
|
||||
}
|
||||
Reference in New Issue
Block a user