Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 14 additions & 47 deletions src/app/AppRootLayoutClient.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
"use client";

import { ApolloProvider } from "@apollo/client";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v16-appRouter";
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
import type { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
import { SnackbarProvider } from "notistack";
import React, { type ReactNode } from "react";
import "symbol-observable";

import { CookieConsentRoot } from "#/features/cookieConsent/ui/CookieConsentRoot";
import { ProjectBrowser } from "#/features/project/ui/ProjectBrowser/ProjectBrowser";
import { ProjectBrowserProvider } from "#/features/project/ui/ProjectBrowser/ProjectBrowserContext";
import { apolloClient } from "#/graphql/apolloClient";
import { type I18nProps } from "#/i18n/getI18nProps";
import type { Locales } from "#/i18n/i18n-types";
import { TrpcProvider } from "#/shared/trpc/TrpcProvider";
import { SnackbarCloseButton } from "#/shared/ui/atoms/SnackbarCloseButton";
import { AppShellProviders } from "#/shared/ui/providers/AppShellProviders";
import { AppRouterI18nProvider } from "#/shared/ui/providers/I18nProvider";
import { StateThemeProvider } from "#/shared/ui/providers/StateThemeProvider";
import { isSnackbarClosable } from "#/shared/ui/snackbarClosability";
import { ReduxProvider } from "#/store/provider";

type AppRootLayoutClientProps = {
children: ReactNode;
Expand All @@ -31,7 +23,7 @@ type AppRootLayoutClientProps = {
};

/**
* Mirrors `pages/_app` providers for App Router routes (default locale home).
* App Router provider shell. Shares {@link AppShellProviders} with Pages `_app`.
*/
export const AppRootLayoutClient: React.FC<AppRootLayoutClientProps> = ({
children,
Expand All @@ -41,43 +33,18 @@ export const AppRootLayoutClient: React.FC<AppRootLayoutClientProps> = ({
}) => {
return (
<AppRouterCacheProvider options={{ key: "css" }}>
<TrpcProvider>
<ReduxProvider>
<SessionProvider session={session}>
<ApolloProvider client={apolloClient}>
<StateThemeProvider>
<SnackbarProvider
maxSnack={4}
action={(snackbarKey) =>
isSnackbarClosable(snackbarKey) ? (
<SnackbarCloseButton snackbarKey={snackbarKey} />
) : null
}
classes={{
containerAnchorOriginBottomLeft:
"snackbar-mobile-bottom-margin",
containerAnchorOriginBottomCenter:
"snackbar-mobile-bottom-margin",
containerAnchorOriginBottomRight:
"snackbar-mobile-bottom-margin",
}}
>
<AppRouterI18nProvider locale={locale} i18n={i18n}>
<CookieConsentRoot>
<ProjectBrowserProvider>
{children}
<ProjectBrowser />
<Analytics />
<SpeedInsights />
</ProjectBrowserProvider>
</CookieConsentRoot>
</AppRouterI18nProvider>
</SnackbarProvider>
</StateThemeProvider>
</ApolloProvider>
</SessionProvider>
</ReduxProvider>
</TrpcProvider>
<AppShellProviders session={session}>
<AppRouterI18nProvider locale={locale} i18n={i18n}>
<CookieConsentRoot>
<ProjectBrowserProvider>
{children}
<ProjectBrowser />
<Analytics />
<SpeedInsights />
</ProjectBrowserProvider>
</CookieConsentRoot>
</AppRouterI18nProvider>
</AppShellProviders>
</AppRouterCacheProvider>
);
};
2 changes: 1 addition & 1 deletion src/app/internal-marketing/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { notFound } from "next/navigation";
import type { Locales } from "#/i18n/i18n-types";
import { locales } from "#/i18n/i18n-util";
import { loadI18nForLocale } from "#/i18n/loadI18nForLocale";
import { authOptions } from "#/pages/api/auth/[...nextauth]";
import { authOptions } from "#/server/auth/authOptions";

import { AppRootLayoutClient } from "#/app/AppRootLayoutClient";

Expand Down
58 changes: 14 additions & 44 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
import { ApolloProvider } from "@apollo/client";
import type { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
import { type AppProps } from "next/app";
import { SnackbarProvider } from "notistack";
import React from "react";
import "symbol-observable";

import { CookieConsentRoot } from "#/features/cookieConsent/ui/CookieConsentRoot";
import { ProjectBrowser } from "#/features/project/ui/ProjectBrowser/ProjectBrowser";
import { ProjectBrowserProvider } from "#/features/project/ui/ProjectBrowser/ProjectBrowserContext";
import { apolloClient } from "#/graphql/apolloClient";
import { type I18nProps } from "#/i18n/getI18nProps";
import { EmotionCacheProvider } from "#/shared/emotion/EmotionCacheContext";
// `_document` is server-only; importing fonts here registers @font-face + CSS vars in the client bundle.
import "#/shared/fonts/appFonts";
import { TrpcProvider } from "#/shared/trpc/TrpcProvider";
import { SnackbarCloseButton } from "#/shared/ui/atoms/SnackbarCloseButton";
import { AppShellProviders } from "#/shared/ui/providers/AppShellProviders";
import { I18nProvider } from "#/shared/ui/providers/I18nProvider";
import { StateThemeProvider } from "#/shared/ui/providers/StateThemeProvider";
import { isSnackbarClosable } from "#/shared/ui/snackbarClosability";
import { ReduxProvider } from "#/store/provider";
import type { SsrDeviceType } from "#/themes";

import "#/styles/globals.css";
Expand All @@ -35,41 +27,19 @@ type MyAppProps = {
const MyApp: React.FC<AppProps<MyAppProps>> = ({ Component, pageProps }) => {
return (
<EmotionCacheProvider>
<TrpcProvider>
<ReduxProvider>
<SessionProvider session={pageProps.session}>
<ApolloProvider client={apolloClient}>
<StateThemeProvider ssrDeviceType={pageProps.ssrDeviceType}>
<SnackbarProvider
maxSnack={4}
action={(snackbarKey) =>
isSnackbarClosable(snackbarKey) ? (
<SnackbarCloseButton snackbarKey={snackbarKey} />
) : null
}
classes={{
containerAnchorOriginBottomLeft:
"snackbar-mobile-bottom-margin",
containerAnchorOriginBottomCenter:
"snackbar-mobile-bottom-margin",
containerAnchorOriginBottomRight:
"snackbar-mobile-bottom-margin",
}}
>
<I18nProvider i18n={pageProps.i18n}>
<CookieConsentRoot>
<ProjectBrowserProvider>
<Component {...pageProps} />
<ProjectBrowser />
</ProjectBrowserProvider>
</CookieConsentRoot>
</I18nProvider>
</SnackbarProvider>
</StateThemeProvider>
</ApolloProvider>
</SessionProvider>
</ReduxProvider>
</TrpcProvider>
<AppShellProviders
session={pageProps.session}
ssrDeviceType={pageProps.ssrDeviceType}
>
<I18nProvider i18n={pageProps.i18n}>
<CookieConsentRoot>
<ProjectBrowserProvider>
<Component {...pageProps} />
<ProjectBrowser />
</ProjectBrowserProvider>
</CookieConsentRoot>
</I18nProvider>
</AppShellProviders>
</EmotionCacheProvider>
);
};
Expand Down
37 changes: 3 additions & 34 deletions src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,7 @@
// Prisma adapter for NextAuth, optional and can be removed
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import NextAuth, { type NextAuthOptions } from "next-auth";
import GHProvider from "next-auth/providers/github";
// import DiscordProvider from "next-auth/providers/discord";
import GoogleProvider from "next-auth/providers/google";
import NextAuth from "next-auth";

import { env } from "#/env/server.mjs";
import { db } from "#/server/db/client";
import { authOptions } from "#/server/auth/authOptions";

export const authOptions: NextAuthOptions = {
// Include user.id on session
callbacks: {
session({ session, user }) {
Object.assign(session.user, user);
return session;
},
},
// Configure one or more authentication providers
adapter: PrismaAdapter(db as any), // TODO: Remove `as any` when @next-auth/prisma-adapter fixes types
providers: [
// DiscordProvider({
// clientId: env.DISCORD_CLIENT_ID,
// clientSecret: env.DISCORD_CLIENT_SECRET,
// }),
GHProvider({
clientId: env.GITHUB_CLIENT_ID ?? "",
clientSecret: env.GITHUB_CLIENT_SECRET ?? "",
}),
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID ?? "",
clientSecret: env.GOOGLE_CLIENT_SECRET ?? "",
}),
// ...add more providers here
],
};
export { authOptions };

export default NextAuth(authOptions);
37 changes: 37 additions & 0 deletions src/server/auth/authOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Prisma adapter for NextAuth, optional and can be removed
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { type NextAuthOptions } from "next-auth";
import GHProvider from "next-auth/providers/github";
// import DiscordProvider from "next-auth/providers/discord";
import GoogleProvider from "next-auth/providers/google";

import { env } from "#/env/server.mjs";
import { db } from "#/server/db/client";

/** Shared NextAuth config for Pages API routes and App Router RSC layouts. */
export const authOptions: NextAuthOptions = {
// Include user.id on session
callbacks: {
session({ session, user }) {
Object.assign(session.user, user);
return session;
},
},
// Configure one or more authentication providers
adapter: PrismaAdapter(db as any), // TODO: Remove `as any` when @next-auth/prisma-adapter fixes types
providers: [
// DiscordProvider({
// clientId: env.DISCORD_CLIENT_ID,
// clientSecret: env.DISCORD_CLIENT_SECRET,
// }),
GHProvider({
clientId: env.GITHUB_CLIENT_ID ?? "",
clientSecret: env.GITHUB_CLIENT_SECRET ?? "",
}),
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID ?? "",
clientSecret: env.GOOGLE_CLIENT_SECRET ?? "",
}),
// ...add more providers here
],
};
2 changes: 1 addition & 1 deletion src/server/auth/get-server-auth-session.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type GetServerSidePropsContext } from "next";
import { getServerSession } from "next-auth";

import { authOptions } from "#/pages/api/auth/[...nextauth]";
import { authOptions } from "#/server/auth/authOptions";

/**
* Wrapper for getServerSession https://next-auth.js.org/configuration/nextjs
Expand Down
60 changes: 60 additions & 0 deletions src/shared/ui/providers/AppShellProviders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client";

import { ApolloProvider } from "@apollo/client";
import type { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
import { SnackbarProvider } from "notistack";
import React, { type ReactNode } from "react";

import { apolloClient } from "#/graphql/apolloClient";
import { TrpcProvider } from "#/shared/trpc/TrpcProvider";
import { SnackbarCloseButton } from "#/shared/ui/atoms/SnackbarCloseButton";
import { StateThemeProvider } from "#/shared/ui/providers/StateThemeProvider";
import { isSnackbarClosable } from "#/shared/ui/snackbarClosability";
import { ReduxProvider } from "#/store/provider";
import type { SsrDeviceType } from "#/themes";

type AppShellProvidersProps = {
children: ReactNode;
session: Session | null;
ssrDeviceType?: SsrDeviceType;
};

/**
* Shared client provider stack for Pages `_app` and App Router layouts.
* Locale/i18n and router-specific cache wrappers stay outside this tree.
*/
export const AppShellProviders: React.FC<AppShellProvidersProps> = ({
children,
session,
ssrDeviceType,
}) => (
<TrpcProvider>
<ReduxProvider>
<SessionProvider session={session}>
<ApolloProvider client={apolloClient}>
<StateThemeProvider ssrDeviceType={ssrDeviceType}>
<SnackbarProvider
maxSnack={4}
action={(snackbarKey) =>
isSnackbarClosable(snackbarKey) ? (
<SnackbarCloseButton snackbarKey={snackbarKey} />
) : null
}
classes={{
containerAnchorOriginBottomLeft:
"snackbar-mobile-bottom-margin",
containerAnchorOriginBottomCenter:
"snackbar-mobile-bottom-margin",
containerAnchorOriginBottomRight:
"snackbar-mobile-bottom-margin",
}}
>
{children}
</SnackbarProvider>
</StateThemeProvider>
</ApolloProvider>
</SessionProvider>
</ReduxProvider>
</TrpcProvider>
);
8 changes: 5 additions & 3 deletions vibe-docs/Instant-Navigations-TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

## Phase 1 — 16.3.x on Vercel

- [x] Tried `next@16.3.0` — local OK; Vercel preview `/api/*` → `/en/404`
- [x] **Pinned back to `next@16.2.12`** so previews + App Router pilot can be verified
- [ ] Re-try latest 16.3.x when adapter-vercel i18n API fix is in a release that works on Turbopack previews
- [x] Tried `next@16.3.0` — local OK; Vercel preview `/api/*` → `/en/404` (2026-08, pre-adapter fix)
- [x] Re-tried on PR #165 preview (2026-08-09) — **same regression** (`x-matched-path: /en/404`)
- [x] **Stay on `next@16.2.12`** until Vercel + Pages `i18n` API routing is fixed
- [ ] Preview smoke green on 16.3.x before enabling Instant Nav flags

## Phase 2 — App Router pilot
Expand All @@ -29,6 +29,8 @@
- [ ] `cacheComponents` / `partialPrefetching` (blocked: root `headers()` + need 16.3)
- [ ] `unstable_instant` on pilot routes (blocked until `cacheComponents`)
- [x] Remove unused `@trpc/next` dependency
- [x] Extract `authOptions` to `src/server/auth/authOptions.ts`
- [x] Extract `AppShellProviders` shared by `_app` and `AppRootLayoutClient`

## Phase 3+ — Playground / full migration

Expand Down
Loading