fix: upgrade to nextjs 16 (#888)

* Upgrade to nextjs 16

* Update sentry config

* Force dynamic for health route

* Upgrade eslint config

* Upgrade jest

* Move types to dev dependencies

* Remove pages from tailwind config

* Replace img with next image
This commit is contained in:
Sergey Mankovsky
2026-02-27 17:18:03 +01:00
committed by GitHub
parent 7f9ce7f13a
commit f6cc03286b
20 changed files with 1077 additions and 916 deletions

1
www/.gitignore vendored
View File

@@ -46,3 +46,4 @@ openapi-ts-error-*.log
# pnpm
.pnpm-store
/v10

View File

@@ -1,5 +1,6 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faClose } from "@fortawesome/free-solid-svg-icons";
import type { JSX } from "react";
import { MouseEventHandler } from "react";
type ModalProps = {

View File

@@ -1,3 +1,5 @@
"use client";
import React from "react";
import { Box, Stack, Link, Heading } from "@chakra-ui/react";
import NextLink from "next/link";

View File

@@ -1,3 +1,5 @@
"use client";
import React, { useState } from "react";
import {
Box,

View File

@@ -1,4 +1,4 @@
import { Container, Flex, Link } from "@chakra-ui/react";
import { Container, Flex } from "@chakra-ui/react";
import { featureEnabled } from "../lib/features";
import NextLink from "next/link";
import Image from "next/image";
@@ -30,7 +30,7 @@ export default async function AppLayout({
mt="1"
>
{/* Logo on the left */}
<Link as={NextLink} href="/" className="flex">
<NextLink href="/" className="flex">
<Image
src="/reach.svg"
width={32}
@@ -46,22 +46,18 @@ export default async function AppLayout({
Capture the signal, not the noise
</p>
</div>
</Link>
</NextLink>
<div>
{/* Text link on the right */}
<Link
as={NextLink}
href={RECORD_A_MEETING_URL}
className="font-light px-2"
>
<NextLink href={RECORD_A_MEETING_URL} className="font-light px-2">
Create
</Link>
</NextLink>
{featureEnabled("browse") ? (
<>
&nbsp;·&nbsp;
<Link href="/browse" as={NextLink} className="font-light px-2">
<NextLink href="/browse" className="font-light px-2">
Browse
</Link>
</NextLink>
</>
) : (
<></>
@@ -69,9 +65,9 @@ export default async function AppLayout({
{featureEnabled("rooms") ? (
<>
&nbsp;·&nbsp;
<Link href="/rooms" as={NextLink} className="font-light px-2">
<NextLink href="/rooms" className="font-light px-2">
Rooms
</Link>
</NextLink>
</>
) : (
<></>
@@ -79,13 +75,9 @@ export default async function AppLayout({
{featureEnabled("requireLogin") ? (
<>
&nbsp;·&nbsp;
<Link
href="/settings/api-keys"
as={NextLink}
className="font-light px-2"
>
<NextLink href="/settings/api-keys" className="font-light px-2">
Settings
</Link>
</NextLink>
&nbsp;·&nbsp;
<UserInfo />
</>

View File

@@ -28,7 +28,7 @@ function WherebyConsentDialogButton({
meetingId: MeetingId;
recordingType: Meeting["recording_type"];
skipConsent: boolean;
wherebyRef: React.RefObject<HTMLElement>;
wherebyRef: React.RefObject<HTMLElement | null>;
}) {
const previousFocusRef = useRef<HTMLElement | null>(null);

View File

@@ -49,8 +49,8 @@ export type RoomDetails = {
// stages: we focus on the consent, then whereby steals focus, then we focus on the consent again, then return focus to whoever stole it initially
const useConsentWherebyFocusManagement = (
acceptButtonRef: RefObject<HTMLButtonElement>,
wherebyRef: RefObject<HTMLElement>,
acceptButtonRef: RefObject<HTMLButtonElement | null>,
wherebyRef: RefObject<HTMLElement | null>,
) => {
const currentFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
@@ -87,7 +87,7 @@ const useConsentWherebyFocusManagement = (
const useConsentDialog = (
meetingId: MeetingId,
wherebyRef: RefObject<HTMLElement> /*accessibility*/,
wherebyRef: RefObject<HTMLElement | null> /*accessibility*/,
) => {
const { state: consentState, touch, hasAnswered } = useRecordingConsent();
// toast would open duplicates, even with using "id=" prop
@@ -220,7 +220,7 @@ function ConsentDialogButton({
wherebyRef,
}: {
meetingId: MeetingId;
wherebyRef: React.RefObject<HTMLElement>;
wherebyRef: React.RefObject<HTMLElement | null>;
}) {
const { showConsentModal, consentState, hasAnswered, consentLoading } =
useConsentDialog(meetingId, wherebyRef);

View File

@@ -1,6 +1,14 @@
import NextAuth from "next-auth";
import { authOptions } from "../../../lib/authBackend";
const handler = NextAuth(authOptions());
export const dynamic = "force-dynamic";
export { handler as GET, handler as POST };
// authOptions() is deferred to request time to avoid calling getNextEnvVar
// during Turbopack's build-phase module evaluation (Next.js 16+)
export function GET(req: Request, ctx: any) {
return NextAuth(authOptions())(req as any, ctx);
}
export function POST(req: Request, ctx: any) {
return NextAuth(authOptions())(req as any, ctx);
}

View File

@@ -1,5 +1,7 @@
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET() {
const health = {
status: "healthy",

View File

@@ -24,10 +24,9 @@ export const viewport: Viewport = {
maximumScale: 1,
};
export function generateMetadata(): Metadata {
const SITE_URL = getNextEnvVar("SITE_URL");
const env = getClientEnv();
export const metadata: Metadata = {
return {
metadataBase: new URL(SITE_URL),
title: {
template: "%s Reflector",
@@ -38,7 +37,9 @@ export const metadata: Metadata = {
applicationName: "Reflector",
referrer: "origin-when-cross-origin",
keywords: ["Reflector", "Monadical", "AI", "Meetings", "Transcription"],
authors: [{ name: "Monadical Team", url: "https://monadical.com/team.html" }],
authors: [
{ name: "Monadical Team", url: "https://monadical.com/team.html" },
],
formatDetection: {
email: false,
address: false,
@@ -65,14 +66,21 @@ export const metadata: Metadata = {
shortcut: "/r-icon.png",
apple: "/r-icon.png",
},
robots: { index: false, follow: false, noarchive: true, noimageindex: true },
robots: {
index: false,
follow: false,
noarchive: true,
noimageindex: true,
},
};
}
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const env = getClientEnv();
return (
<html lang="en" className={poppins.className} suppressHydrationWarning>
<body

View File

@@ -84,7 +84,7 @@ export const getClientEnvServer = (): ClientEnvCommon => {
if (isBuildPhase) {
return {
API_URL: getNextEnvVar("API_URL"),
API_URL: parseNonEmptyString(process.env.API_URL ?? ""),
WEBSOCKET_URL: parseMaybeNonEmptyString(process.env.WEBSOCKET_URL ?? ""),
AUTH_PROVIDER: parseAuthProvider(),
SENTRY_DSN: parseMaybeNonEmptyString(

View File

@@ -1,3 +1,5 @@
import type { JSX } from "react";
type SimpleProps = {
children: JSX.Element | string | (JSX.Element | string)[];
className?: string;

View File

@@ -159,7 +159,7 @@ export default function WebinarPage(details: WebinarDetails) {
<div className="max-w-4xl mx-auto px-2 py-8 bg-gray-50">
<div className="bg-white rounded-3xl px-4 md:px-36 py-4 shadow-md mx-auto">
<Link href="https://www.monadical.com" target="_blank">
<img
<Image
src="/monadical-black-white 1.svg"
alt="Monadical Logo"
className="mx-auto mb-8"
@@ -355,7 +355,7 @@ export default function WebinarPage(details: WebinarDetails) {
<div className="max-w-4xl mx-auto px-2 py-8 bg-gray-50">
<div className="bg-white rounded-3xl px-4 md:px-36 py-4 shadow-md mx-auto">
<Link href="https://www.monadical.com" target="_blank">
<img
<Image
src="/monadical-black-white 1.svg"
alt="Monadical Logo"
className="mx-auto mb-8"

View File

@@ -4,47 +4,20 @@ const nextConfig = {
env: {
IS_CI: process.env.IS_CI,
},
};
module.exports = nextConfig;
// Injected content via Sentry wizard below
const { withSentryConfig } = require("@sentry/nextjs");
module.exports = withSentryConfig(
module.exports,
{
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
// Suppresses source map uploading logs during build
silent: true,
org: "monadical",
project: "reflector-www",
},
{
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Transpiles SDK to be compatible with IE11 (increases bundle size)
transpileClientSDK: true,
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
tunnelRoute: "/monitoring",
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
experimental: {
optimizePackageImports: ["@chakra-ui/react"],
},
};
const { withSentryConfig } = require("@sentry/nextjs");
module.exports = withSentryConfig(nextConfig, {
silent: true,
org: "monadical",
project: "reflector-www",
widenClientFileUpload: true,
tunnelRoute: "/monitoring",
bundleSizeOptimizations: {
excludeDebugStatements: true,
},
);
});

View File

@@ -21,17 +21,16 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@sentry/nextjs": "^10.40.0",
"@tanstack/react-query": "^5.85.9",
"@types/ioredis": "^5.0.0",
"@whereby.com/browser-sdk": "^3.3.4",
"autoprefixer": "10.4.20",
"axios": "^1.13.5",
"eslint": "^9.33.0",
"eslint-config-next": "^15.5.3",
"eslint-config-next": "^16.1.6",
"fontawesome": "^5.6.3",
"ioredis": "^5.7.0",
"jest-worker": "^29.6.2",
"lucide-react": "^0.525.0",
"next": "^15.5.10",
"next": "^16.1.6",
"next-auth": "^4.24.12",
"next-themes": "^0.4.6",
"nuqs": "^2.4.3",
@@ -39,8 +38,8 @@
"openapi-react-query": "^0.5.0",
"postcss": "8.4.31",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-dropdown": "^1.11.0",
"react-icons": "^5.0.1",
"react-markdown": "^9.0.0",
@@ -61,12 +60,14 @@
"author": "Andreas <andreas@monadical.com>",
"license": "All Rights Reserved",
"devDependencies": {
"@types/ioredis": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/react": "18.2.20",
"jest": "^30.1.3",
"@types/react": "19.2.14",
"@types/react-dom": "^19.2.3",
"jest": "^30.2.0",
"openapi-typescript": "^7.9.1",
"prettier": "^3.0.0",
"ts-jest": "^29.4.1"
"ts-jest": "^29.4.6"
},
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
}

1754
www/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@ module.exports = {
preflight: false,
},
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],

View File

@@ -13,7 +13,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"plugins": [
{
"name": "next"
@@ -22,6 +22,12 @@
"strictNullChecks": true,
"downlevelIteration": true
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules"]
}

View File

@@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1