diff --git a/apps/web/app/home/(user)/_lib/server/load-user-workspace.ts b/apps/web/app/home/(user)/_lib/server/load-user-workspace.ts index 8ddca670c..f9d0ea0ea 100644 --- a/apps/web/app/home/(user)/_lib/server/load-user-workspace.ts +++ b/apps/web/app/home/(user)/_lib/server/load-user-workspace.ts @@ -1,12 +1,10 @@ import { cache } from 'react'; -import { redirect } from 'next/navigation'; - import { createAccountsApi } from '@kit/accounts/api'; -import { requireUser } from '@kit/supabase/require-user'; import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import featureFlagsConfig from '~/config/feature-flags.config'; +import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component'; const shouldLoadAccounts = featureFlagsConfig.enableTeamAccounts; @@ -30,18 +28,12 @@ async function workspaceLoader() { const workspacePromise = api.getAccountWorkspace(); - const [accounts, workspace, auth] = await Promise.all([ + const [accounts, workspace, user] = await Promise.all([ accountsPromise(), workspacePromise, - requireUser(client), + requireUserInServerComponent(), ]); - if (!auth.data) { - return redirect(auth.redirectTo); - } - - const user = auth.data; - return { accounts, workspace, diff --git a/apps/web/app/home/(user)/billing/page.tsx b/apps/web/app/home/(user)/billing/page.tsx index c26dc03a1..6ea32ef5f 100644 --- a/apps/web/app/home/(user)/billing/page.tsx +++ b/apps/web/app/home/(user)/billing/page.tsx @@ -1,12 +1,8 @@ -import { redirect } from 'next/navigation'; - import { BillingPortalCard, CurrentLifetimeOrderCard, CurrentSubscriptionCard, } from '@kit/billing-gateway/components'; -import { requireUser } from '@kit/supabase/require-user'; -import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import { If } from '@kit/ui/if'; import { PageBody } from '@kit/ui/page'; import { Trans } from '@kit/ui/trans'; @@ -14,6 +10,7 @@ import { Trans } from '@kit/ui/trans'; import billingConfig from '~/config/billing.config'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; +import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component'; // local imports import { HomeLayoutPageHeader } from '../_components/home-page-header'; @@ -31,16 +28,9 @@ export const generateMetadata = async () => { }; async function PersonalAccountBillingPage() { - const client = getSupabaseServerComponentClient(); - const auth = await requireUser(client); + const user = await requireUserInServerComponent(); - if (auth.error) { - redirect(auth.redirectTo); - } - - const [data, customerId] = await loadPersonalAccountBillingPageData( - auth.data.id, - ); + const [data, customerId] = await loadPersonalAccountBillingPageData(user.id); return ( <> diff --git a/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts b/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts index 9154fc87e..40a204f4d 100644 --- a/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts +++ b/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts @@ -4,11 +4,11 @@ import { cache } from 'react'; import { redirect } from 'next/navigation'; -import { requireUser } from '@kit/supabase/require-user'; import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import { createTeamAccountsApi } from '@kit/team-accounts/api'; import pathsConfig from '~/config/paths.config'; +import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component'; export type TeamAccountWorkspace = Awaited< ReturnType @@ -29,9 +29,9 @@ async function workspaceLoader(accountSlug: string) { const client = getSupabaseServerComponentClient(); const api = createTeamAccountsApi(client); - const [workspace, auth] = await Promise.all([ + const [workspace, user] = await Promise.all([ api.getAccountWorkspace(accountSlug), - requireUser(client), + requireUserInServerComponent(), ]); // we cannot find any record for the selected account @@ -40,12 +40,6 @@ async function workspaceLoader(accountSlug: string) { return redirect(pathsConfig.app.home); } - if (!auth.data) { - return redirect(auth.redirectTo); - } - - const user = auth.data; - return { ...workspace.data, user, diff --git a/apps/web/app/home/[account]/billing/return/page.tsx b/apps/web/app/home/[account]/billing/return/page.tsx index 5081450cf..072260f54 100644 --- a/apps/web/app/home/[account]/billing/return/page.tsx +++ b/apps/web/app/home/[account]/billing/return/page.tsx @@ -9,6 +9,7 @@ import { getSupabaseServerComponentClient } from '@kit/supabase/server-component import billingConfig from '~/config/billing.config'; import { withI18n } from '~/lib/i18n/with-i18n'; +import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component'; interface SessionPageProps { searchParams: { @@ -73,13 +74,9 @@ function BlurryBackdrop() { } async function loadCheckoutSession(sessionId: string) { + await requireUserInServerComponent(); + const client = getSupabaseServerComponentClient(); - const { error } = await requireUser(client); - - if (error) { - throw new Error('Authentication required'); - } - const gateway = await getBillingGatewayProvider(client); const session = await gateway.retrieveCheckoutSession({ diff --git a/apps/web/app/update-password/page.tsx b/apps/web/app/update-password/page.tsx index a8fffd936..fea8e33d5 100644 --- a/apps/web/app/update-password/page.tsx +++ b/apps/web/app/update-password/page.tsx @@ -1,14 +1,11 @@ -import { redirect } from 'next/navigation'; - import { UpdatePasswordForm } from '@kit/auth/password-reset'; import { AuthLayoutShell } from '@kit/auth/shared'; -import { requireUser } from '@kit/supabase/require-user'; -import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import { AppLogo } from '~/components/app-logo'; import pathsConfig from '~/config/paths.config'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; +import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component'; export const generateMetadata = async () => { const { t } = await createI18nServerInstance(); @@ -19,13 +16,7 @@ export const generateMetadata = async () => { }; async function UpdatePasswordPage() { - const client = getSupabaseServerComponentClient(); - const auth = await requireUser(client); - - // we require the user to be logged in to access this page - if (auth.error) { - redirect(auth.redirectTo); - } + await requireUserInServerComponent(); return ( diff --git a/apps/web/lib/server/require-user-in-server-component.ts b/apps/web/lib/server/require-user-in-server-component.ts new file mode 100644 index 000000000..6e87274f7 --- /dev/null +++ b/apps/web/lib/server/require-user-in-server-component.ts @@ -0,0 +1,25 @@ +import 'server-only'; + +import { cache } from 'react'; + +import { redirect } from 'next/navigation'; + +import { requireUser } from '@kit/supabase/require-user'; +import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; + +/** + * @name requireUserInServerComponent + * @description Require the user to be authenticated in a server component. + * We reuse this function in multiple server components - it is cached so that the data is only fetched once per request. + * Use this instead of `requireUser` in server components, so you don't need to hit the database multiple times in a single request. + */ +export const requireUserInServerComponent = cache(async () => { + const client = getSupabaseServerComponentClient(); + const result = await requireUser(client); + + if (result.error) { + redirect(result.redirectTo); + } + + return result.data; +}); diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 4e376998a..e9cc3dd91 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -30,6 +30,11 @@ const config = { images: { remotePatterns: getRemotePatterns(), }, + logging: { + fetches: { + fullUrl: true, + }, + }, experimental: { mdxRs: true, instrumentationHook: true,