Skip to content

Upgrade to Next 16 and clear every dependency advisory - #74

Merged
invisiblemask merged 7 commits into
restructure/iq-hiiq-scopefrom
chore/upgrade-next-16
Aug 21, 2026
Merged

Upgrade to Next 16 and clear every dependency advisory#74
invisiblemask merged 7 commits into
restructure/iq-hiiq-scopefrom
chore/upgrade-next-16

Conversation

@invisiblemask

@invisiblemask invisiblemask commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes https://github.com/IQIndustries/issues/issues/5216 & https://github.com/IQIndustries/issues/issues/5217 when this PR merged

Pins the app to Next 16.3.1 and takes pnpm audit from 104 advisories to 0.

15.4.x is an unmaintained patch line — the maintained lines are 16.x and the 15.5.23 backport. 25 of the original advisories were against next itself, the earliest fixed in 15.4.11. Going to 16 rather than the backport avoids sitting on an aging line and pulls the next-intl v4 migration forward instead of deferring it.

What changed

cf3516f — drop unused deps and the stale eslint setup

Removes @emotion/react, @emotion/styled, @react-three/drei, @react-three/fiber, three and @types/three, none of which were imported. eslint and eslint-config-next were in dependencies and entirely unused — linting runs through Biome, and next lint is removed in 16 — so .eslintrc.json goes with them. Also drops the dead next export script and bumps axios (30+ advisories) and the pinned postcss (high-severity path traversal). 328 packages removed.

6d02cb5 — Next 16, React 19 and next-intl 4

  • src/middleware.tssrc/proxy.ts with a named proxy export, per the Next 16 rename.
  • next-intl 3 → 4: global.d.ts moves to the AppConfig augmentation, and NextIntlClientProvider no longer takes messages — it inherits them from i18n/request.ts.
  • React 18.2 → 19.2.8 with matching @types/*. Optional for Next 16, but the repo was already carrying @types/react 19 against React 18.
  • data-scroll-behavior="smooth" on <html>, since globals.css sets scroll-behavior: smooth and Next 16 no longer overrides it during route transitions.
  • suppressHydrationWarning on <html> — next-themes 0.4 writes class and color-scheme before hydration.
  • src/app/favicon.icopublic/favicon.ico. As an app-root metadata route it failed the build outright (Cannot find module for page: /favicon.ico) because there is no root layout.tsx, only [locale]/layout.tsx. The layout already declares the icon explicitly.

cdf2af9 — leaf bumps, dead deps, pinned build transitives

GSAP turned out to be unused — no imports anywhere — so gsap, @gsap/react and @radix-ui/react-menubar are gone. The pnpm.overrides block clears the last transitive advisories, all reachable only through tailwindcss 3's build-time deps (sucrase, chokidar, postcss-load-config); it carries a note to delete it with the Tailwind 4 upgrade.

de858e3 — stop PostHog probing versioned asset paths

posthog-js 1.418 added strict_script_versioning, defaulting to "fallback": it requests /static/<version>/<extension>.js first and only retries the unversioned URL after that fails. The assets CDN does not serve the versioned path, so every page load spent two 404s before loading surveys and dead-click autocapture.

68398e0 — proxy PostHog through /ingest, emit real canonicals

Two pre-existing defects rather than upgrades, both in the same area as the commit above.

/ingest/* was redirects with permanent: true. posthog-js is initialised with api_host: "/ingest", so those paths have to be proxied server-side; a redirect handed the browser us.i.posthog.com and defeated the proxy outright. Now rewrites. permanent: true also meant browsers cached the redirect indefinitely, so returning visitors would have kept bypassing the proxy the longer it sat.

The layout separately carried <link rel="canonical" /> with no href, which did nothing. It can't be fixed in place — a layout has no access to the pathname, so any canonical it emits would point /hiiq at /. Both pages now derive their own from next-intl's getPathname, plus a reciprocal hreflang set. getPathname was already available from createNavigation and simply wasn't destructured, so the locale prefix rules stay in i18n/routing.ts instead of being duplicated. The hidden ko locale is skipped — it aliases kr on the same URL, so listing both would emit two hreflang tags for one page.

One judgement call: hreflang codes come from the existing localization values in messages/_schema.ts, so English is en-US rather than plain en. Region-specific is narrower than needed for a site with one English variant, but reusing that field avoids standing up a second source of truth for locale metadata. Easy to revisit.

37e7e86 — bump live-code majors, replace prettier with biome

framer-motion 12 → 13, lucide-react 0.525 → 1.33, react-icons 4 → 5, plus a posthog-js patch. All four have live consumers, so all four were checked in the browser rather than just compiled.

prettier is removed rather than bumped. Biome already formats through lint-staged, and the format script passed --loglevel, a flag prettier 3 renamed — so it was going to break on that upgrade regardless. The script now runs biome format.

react-intersection-observer is deliberately left on 9.x. Its only consumer is InViewAnimateBottom, which has no importers, and inside it useInView() is destructured without ever attaching the ref — so inView was permanently false and the useAnimation controls were never bound to a component. That code path has never run. Bumping it buys nothing; see the dead-code note below.

Results

Before After
pnpm audit 104 (53 high, 47 moderate, 4 low) 0
pnpm build wall 36.7s ~14s
Compile 12.0s 4.1s

Turbopack is the default bundler for both dev and build in 16. There is no custom webpack config in this repo, so that came for free.

Not every original advisory was live against this app — the Server Actions ones need "use server" (none here), the rewrites SSRF needs dynamic destinations (ours are two static entries), and the self-hosted image-optimizer and custom-server advisories are moot on Vercel. The ones that did apply were RSC cache poisoning and cache confusion, which matter most for a CDN-cached public site, plus Server Components DoS and connection exhaustion.

Verification

  • pnpm build green; pnpm audit reports no known vulnerabilities.
  • All five locale routes return 200 with the correct lang: /→en, /kr→kr, /zh→zh, /hiiq→en, /kr/hiiq→kr.
  • Korean and Chinese messages render, confirming next-intl v4's implicit message inheritance.
  • Radix locale switcher opens; client-side navigation through next-intl's Link works.
  • Live IQ data fetches through the gateway (price 0.0006, mcap 16.01M, supply 27B, 2.5B locked).
  • Browser console clean, zero failed network requests.
  • After the major bumps: 29 SVGs render with no empty or zero-box icons (the one zero-box hit is the mobile hamburger, display: none at desktop width), and the mobile menu opens at 375px with its react-icons close icon drawn.
  • /ingest proxies same-origin, confirmed two ways. In the browser, performance.getEntriesByType('resource') shows three /ingest/* requests (config, surveys, dead-clicks) and zero to posthog.com. By request: fetch('/ingest/static/array.js', {redirect: 'error'}) returns 200 from localhost:3000 with content-type: application/javascript. redirect: 'error' throws on any 3xx, so this cannot pass unless the request is genuinely proxied.
  • Canonical and hreflang output checked on every route:
URL canonical hreflang
/ braindao.org en-US, ko-KR, zh-CN, x-default
/kr braindao.org/kr same set
/zh braindao.org/zh same set
/hiiq braindao.org/hiiq /hiiq, /kr/hiiq, /zh/hiiq
/kr/hiiq braindao.org/kr/hiiq same set

Every page self-canonicalises and the hreflang sets are reciprocal, which is what Google requires to honour them.

Deliberately left out

Kept as a version bump plus the two adjacent bug fixes. Follow-ups worth their own PRs:

  • PPR via experimental.cacheComponents, and unstable_cacheuse cache.
  • Tailwind 3 → 4, which also retires the pnpm.overrides block.
  • src/components/ui/popover.tsx, card.tsx and container-scroll-animation.tsx have no importers. Popover is the notable one: @radix-ui/react-popover is a direct dependency reachable only through that dead file, so the dep cannot go without deleting the component.
  • openGraph.url is still hardcoded to the English URL on both pages, so every locale advertises the same og:url. Same family as the canonical fix, but it is a separate call about og metadata.
  • Remaining majors, each wanting its own change: tailwindcss 3 → 4 (config migration, also retires pnpm.overrides), typescript 5.9 → 7 (the native rewrite), biome 1 → 2 (biome migrate plus repo-wide format churn), and @types/node, which should track the Node 24 runtime rather than jump to 26.
  • src/components/transitions/InViewAnimateBottom.tsx is dead too — no importers — and removing it is what unblocks dropping react-intersection-observer. Same call as the unused ui/ components above, so grouped with them rather than done piecemeal here.

@emotion/*, @react-three/*, three and @types/three had no imports
anywhere in the repo. eslint and eslint-config-next sat in dependencies
rather than devDependencies and were never invoked — linting runs through
Biome via lint-staged, and `next lint` is gone in Next 16 — so they only
served to pull in the minimatch/js-yaml/flatted/ajv advisory chain.

Also bumps axios and unpins postcss to clear their high-severity
advisories, and drops the `export` script (`next export` was removed in
Next 14).

Removes 328 packages. pnpm audit goes from 104 advisories to 47; build
output is byte-identical at 105 kB First Load JS.
Next 15.4.10 was on an unmaintained patch line carrying 25 open
advisories, the earliest of which was fixed in 15.4.11. Moving to 16.3.1
clears all of them rather than chasing the 15.5 backport, which would
still have required the next-intl 4 migration to get here later.

Migration notes:

- middleware.ts becomes proxy.ts with a named `proxy` export, per the
  Next 16 rename. next-intl still exports createMiddleware, so the
  handler itself is unchanged.
- next-intl 4 infers messages from i18n/request.ts, so the explicit
  `messages` prop and the getMessages() call are gone. The type
  augmentation moves from the global IntlMessages interface to
  next-intl's AppConfig.
- globals.css sets scroll-behavior: smooth on html, and Next 16 no longer
  overrides that during route transitions, so html opts back in with
  data-scroll-behavior="smooth".
- next-themes 0.4 writes color-scheme onto html before hydration, which
  React reports as a mismatch, so html gets suppressHydrationWarning.
- favicon.ico moves to public/. As an app-root metadata route it had no
  root layout to attach to and Next 16 failed to resolve it; the layout
  already declared the icon explicitly.
- tsconfig gains the mandatory jsx: react-jsx and the .next/dev types
  path, and the stale next.config.mjs include is corrected to .ts.

Turbopack is now the default bundler: the production build drops from
36.7s to 11.7s, compile from 12.0s to 4.1s. pnpm audit goes from 47
advisories to 13, none of which are in next, next-intl or axios.

Verified: build green, all five locale routes resolve through proxy.ts
with the custom /kr prefix intact, Korean and Chinese messages load,
live IQ token data still fetches, client-side navigation works and the
browser console is clean.
Bumps the remaining direct dependencies to current majors/patches and
drops gsap, @gsap/react and @radix-ui/react-menubar, none of which are
imported anywhere in the tree.

The pnpm.overrides block clears the last transitive advisories, all of
them reached only through tailwindcss 3's build-time deps. `pnpm audit`
now reports no known vulnerabilities, down from 104 at the start of the
upgrade.
posthog-js 1.418 introduced strict_script_versioning, defaulting to
"fallback": it requests /static/<version>/<extension>.js first and only
retries the unversioned URL after that fails. The assets CDN does not
serve the versioned path, so every page load spent two 404s before
loading surveys and dead-click autocapture.
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
braindao-ui Ready Ready Preview Aug 21, 2026 8:40am

Request Review

The /ingest paths were `redirects` with permanent: true, so the browser was
handed the posthog.com URL directly — the exact thing `api_host: "/ingest"`
exists to avoid. Rewrites proxy them server-side instead.

The layout also carried `<link rel="canonical" />` with no href, which did
nothing. Canonicals can't live in the layout because it has no pathname and
would point every route at /, so both pages now build their own from
next-intl's getPathname, along with a reciprocal hreflang set.
framer-motion 13, lucide-react 1, react-icons 5 and a posthog-js patch.
All four have live consumers; icons and the mobile menu verified rendering.

prettier is removed rather than bumped. Biome already formats via
lint-staged, and the `format` script passed `--loglevel`, a flag prettier 3
renamed, so the script was on track to break on the next major anyway. It
now runs biome.

Holding the remaining majors for their own changes: tailwindcss 4 is a
config migration that also retires the pnpm.overrides block, typescript 7
is the native rewrite, biome 2 needs `biome migrate` plus repo-wide format
churn, and @types/node should track the Node 24 runtime rather than jump
to 26.

react-intersection-observer is left alone deliberately: its only consumer,
InViewAnimateBottom, has no importers, and it destructures useInView()
without attaching the ref, so inView was permanently false and the
useAnimation controls were never bound to a component. Bumping dead code
that never ran buys nothing.
Brings in the HiIQ page rewrite from #73. The only conflict was the
import block in app/[locale]/hiiq/page.tsx: the page now composes from
components/layouts/hiiq/* instead of section-kit, so the old section-kit
import goes and `alternatesFor` stays. Git had already merged the body,
keeping generateMetadata's canonical alongside the new section list.
@invisiblemask
invisiblemask merged commit 7bfbb0c into restructure/iq-hiiq-scope Aug 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant