From 8bbbe0a471cd4ee6e83615b34719d1cdf997e534 Mon Sep 17 00:00:00 2001 From: Arnas Donauskas Date: Fri, 17 Jul 2026 15:08:25 +0300 Subject: [PATCH] fix(settings): wrap useSearchParams pages in Suspense so nav works in prod MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settings page derived its active panel from `useSearchParams()` but wasn't wrapped in a Suspense boundary. In Next 16 that fails the production build ("missing Suspense with CSR bailout") and forces the page to bail to full client-side rendering — shipping a settings screen whose rail click handlers never wire up. Because the account-menu Settings link targets `?tab=whatsapp`, users land on the WhatsApp panel and can't navigate to any other section ("locked on WhatsApp"). Wrap the `useSearchParams` consumer in `` on the three affected dashboard pages — settings, inbox, automations/new — mirroring the split login/signup already use. Also add the missing `Settings.sections.quick-replies` label, which was rendering as a raw translation key in the settings rail. Verified: production build now succeeds (previously errored out during static generation); every settings rail section navigates correctly in a production build. Co-Authored-By: Claude Opus 4.8 --- messages/en.json | 1 + src/app/(dashboard)/automations/new/page.tsx | 13 ++++++++++++- src/app/(dashboard)/inbox/page.tsx | 13 ++++++++++++- src/app/(dashboard)/settings/page.tsx | 18 +++++++++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/messages/en.json b/messages/en.json index 07aa121a55..a3dd25ff20 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1501,6 +1501,7 @@ "appearance": "Appearance", "whatsapp": "WhatsApp", "templates": "Templates", + "quick-replies": "Quick replies", "fields": "Fields & tags", "deals": "Deals & currency", "members": "Team members", diff --git a/src/app/(dashboard)/automations/new/page.tsx b/src/app/(dashboard)/automations/new/page.tsx index d707984903..55d5c90439 100644 --- a/src/app/(dashboard)/automations/new/page.tsx +++ b/src/app/(dashboard)/automations/new/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useMemo } from "react" +import { Suspense, useMemo } from "react" import { useSearchParams } from "next/navigation" import { @@ -11,7 +11,18 @@ import { import { AUTOMATION_TEMPLATES, type TemplateSlug } from "@/lib/automations/templates" import type { AutomationStepType, AutomationTriggerType } from "@/types" +// `useSearchParams` requires a Suspense boundary or the production build +// bails to CSR and errors out. Thin wrapper supplies it; the inner +// component reads the `?template=` query string. export default function NewAutomationPage() { + return ( + + + + ) +} + +function NewAutomationPageInner() { const params = useSearchParams() const template = params.get("template") as TemplateSlug | null diff --git a/src/app/(dashboard)/inbox/page.tsx b/src/app/(dashboard)/inbox/page.tsx index b32e74be53..f73a7e2ba3 100644 --- a/src/app/(dashboard)/inbox/page.tsx +++ b/src/app/(dashboard)/inbox/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback, useEffect, useRef } from "react"; +import { Suspense, useState, useCallback, useEffect, useRef } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { createClient } from "@/lib/supabase/client"; @@ -21,7 +21,18 @@ import { cn } from "@/lib/utils"; // across reloads and sessions (device-scoped, like the theme prefs). const CONTACT_PANEL_STORAGE_KEY = "wacrm:inbox:contact-panel-open"; +// `useSearchParams` (the `?c=` deep link below) requires a Suspense +// boundary or the production build bails to CSR and errors out. Thin +// wrapper supplies it; the inner component holds all the inbox state. export default function InboxPage() { + return ( + + + + ); +} + +function InboxPageInner() { const t = useTranslations("Inbox.page"); const router = useRouter(); const searchParams = useSearchParams(); diff --git a/src/app/(dashboard)/settings/page.tsx b/src/app/(dashboard)/settings/page.tsx index 97d192b356..a423383172 100644 --- a/src/app/(dashboard)/settings/page.tsx +++ b/src/app/(dashboard)/settings/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useMemo, type ReactNode } from 'react'; +import { Suspense, useMemo, type ReactNode } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; @@ -23,7 +23,23 @@ import { type SettingsSection, } from '@/components/settings/settings-sections'; +// `useSearchParams` opts this page out of static prerendering unless it +// sits under a Suspense boundary. Without one, the production build hits +// the "missing Suspense with CSR bailout" error and the whole page bails +// to client-side rendering — shipping a settings screen whose rail never +// wires up its click handlers. You land on the section the URL carried +// (the account-menu Settings link points at `?tab=whatsapp`) and can't +// navigate away. Mirror the login/signup split: a thin wrapper supplies +// the boundary; the inner component reads the query string. export default function SettingsPage() { + return ( + + + + ); +} + +function SettingsPageInner() { const router = useRouter(); const searchParams = useSearchParams(); const { defaultCurrency } = useAuth();