Skip to content

Upgrade to Next.js 16 and next-intl 4.9.1#19

Merged
JohnRDOrazio merged 14 commits intomainfrom
feature/nextjs-16-upgrade
Apr 18, 2026
Merged

Upgrade to Next.js 16 and next-intl 4.9.1#19
JohnRDOrazio merged 14 commits intomainfrom
feature/nextjs-16-upgrade

Conversation

@JohnRDOrazio
Copy link
Copy Markdown
Member

@JohnRDOrazio JohnRDOrazio commented Apr 17, 2026

Summary

Closes #18

  • Upgrade Next.js from 15.5.14 to 16.2.4 and next-intl from 4.1.0 to 4.9.1
  • Rename middleware.tsproxy.ts with named export (Next.js 16 convention)
  • Add second argument to revalidateTag() calls (new required signature)
  • Create ESLint flat config (eslint.config.mjs) replacing removed next lint
  • Fix all pre-existing lint errors surfaced by stricter React 19.2 / TypeScript rules

Changes

File Change
package.json Bump next, next-intl, react, eslint-config-next; lint script → eslint .
middleware.tsproxy.ts Renamed, export defaultexport const proxy
eslint.config.mjs New ESLint flat config (core-web-vitals + typescript)
app/api/*/route.ts revalidateTag(tag)revalidateTag(tag, { expire: 0 })
app/[lang]/layout.tsx, src/i18n/request.ts Remove as any casts
components/blog/ShareButtons.tsx Derive URL, use useSyncExternalStore for canShare
components/sections/FishBackground.tsx Lazy useState initializer (avoids impure render)
components/projects/RepoLanguages.tsx Proper loading state init + fetch cleanup
components/sections/HeroBanner.tsx <img><Image> for logo
3 modal components formDataRef (ref) → formData (state)

Test plan

  • npm run build passes
  • npm run lint passes clean (0 errors, 0 warnings)
  • npm run dev starts on Next.js 16.2.4 (Turbopack)
  • All pages render correctly
  • Form modals work correctly with state-based form data
  • Share buttons work on blog/project pages
  • Fish background renders on home page

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added ESLint flat configuration.
  • Bug Fixes

    • Improved modal form state/reset for more reliable submissions.
    • More accurate loading and cancellation behavior during project data fetches.
    • Adjusted cache revalidation to ensure immediate tag expiry where applicable.
  • Improvements

    • Deterministic background visuals for consistent rendering.
    • Native share button gating refined for better share availability.
    • Optimized hero logo rendering.
  • Chores

    • Upgraded Next.js, React, and related tooling; updated lint script.

JohnRDOrazio and others added 2 commits April 18, 2026 01:34
- Rename middleware.ts to proxy.ts with named export (Next.js 16 convention)
- Add second argument to revalidateTag calls (new required signature)
- Create ESLint flat config (eslint.config.mjs) and update lint script
- Fix all lint errors: replace refs-during-render with state, derive
  values instead of setState-in-effect, remove `as any` casts,
  use next/image for logo SVG

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Warning

Rate limit exceeded

@JohnRDOrazio has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 0 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 44 minutes and 0 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 111657df-7921-480b-8871-4000a6de5cc2

📥 Commits

Reviewing files that changed from the base of the PR and between 1d07d86 and 66ce675.

📒 Files selected for processing (3)
  • components/projects/RepoLanguages.tsx
  • components/sections/FishBackground.tsx
  • components/sections/ReferCommunityProjectModal.tsx
📝 Walkthrough

Walkthrough

Upgrades Next.js and React dependencies, adds an ESLint flat config, renames middleware to a named proxy export, changes cache revalidation calls to include { expire: 0 }, and refactors several components to address React/TypeScript lint rules (refs → state, deterministic RNG, useSyncExternalStore).

Changes

Cohort / File(s) Summary
Framework & Dependency Bumps
package.json, tsconfig.json
Bumped next, next-intl, react, react-dom, types and ESLint packages; switched JSX to react-jsx; adjusted TS include paths and formatting. Updated lint script to run eslint ..
ESLint Flat Config
eslint.config.mjs
Added an exported flat ESLint config that composes eslint-config-next presets and sets global ignore patterns.
Middleware / Proxy Export
proxy.ts
Changed Next middleware export from default to named proxy (export const proxy = createMiddleware(...)), altering module export shape.
Locale Validation Fixes
app/[lang]/layout.tsx, src/i18n/request.ts
Replaced permissive as any locale casts with (routing.locales as readonly string[]).includes(...) for stricter TypeScript correctness; runtime behavior unchanged.
Cache Revalidation Signature Updates
app/api/revalidate/route.ts, app/api/stats/route.ts
Now call revalidateTag(tag, { expire: 0 }) / revalidateTag('site-stats', { expire: 0 }) to match updated API signature.
Share / External Store
components/blog/ShareButtons.tsx
Replaced effect/state with useSyncExternalStore selectors for canShare and derived url (server snapshot returns false).
Repo Languages Fetching
components/projects/RepoLanguages.tsx
Removed explicit loading state in favor of derived loading; added AbortController cancellation; track fetchedKey to avoid redundant fetches.
Deterministic Background Generation
components/sections/FishBackground.tsx
Switched from post-mount randomization to useMemo with seeded PRNG (createSeededRng) so fish placement is deterministic per count.
Image Component Update
components/sections/HeroBanner.tsx
Replaced native <img> with Next.js <Image> using explicit width/height and unoptimized flag.
Form Ref → State Refactors
components/sections/ReferCommunityProjectModal.tsx, components/sections/ReferLocalGroupModal.tsx, components/sections/SubmitProjectModal.tsx
Converted mutable formDataRef usages to useState formData and added formKey to force form remounts; updated API payloads and submit handlers to read from state.
Misc. Type/Formatting Tweaks
tsconfig.json, miscellaneous small edits
Formatting and small type-safety adjustments across TS/JS files (minor line-level edits).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code with careful paws,

bumped Next and React and fixed the laws.
Refs turned to state, RNG tamed with seed,
lint cleared the meadow — a tidy deed! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: upgrading Next.js and next-intl to specific versions.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #18: upgraded Next.js/next-intl/dependencies, renamed middleware with named export, updated revalidateTag calls, migrated to ESLint flat config, and fixed linting errors.
Out of Scope Changes check ✅ Passed All changes align with the upgrade objectives: dependency updates, middleware renaming, revalidateTag modifications, ESLint migration, and necessary component fixes for React 19/TypeScript compatibility.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/nextjs-16-upgrade

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 17, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 8 complexity · 2 duplication

Metric Results
Complexity 8
Duplication 2

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/sections/ReferLocalGroupModal.tsx (1)

21-27: ⚠️ Potential issue | 🟡 Minor

openDialog doesn't reset formData — inconsistent with the sibling modals.

ReferCommunityProjectModal.openDialog and SubmitProjectModal.openDialog both call setFormData({...}) to clear the snapshot, but this one doesn't. Once a user submits (or opens awaiting_code then closes), reopening the modal will render the form with the previous submission prefilled via defaultValue={formData.*} on lines 293/307/322/337/354/369, which diverges from the other two modals' behavior. If the intent is to always start fresh (matching the sibling modals), add a reset here.

🔧 Proposed reset in openDialog
   const openDialog = useCallback(() => {
     setStatus('idle')
     setVerificationCode('')
     setCodeError('')
+    setFormData({})
     openedAtRef.current = Date.now()
     dialogRef.current?.showModal()
   }, [])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/sections/ReferLocalGroupModal.tsx` around lines 21 - 27,
openDialog in ReferLocalGroupModal currently resets status, verificationCode,
codeError and openedAtRef but does not reset formData, causing stale values to
reappear via defaultValue bindings (formData.*). Update
ReferLocalGroupModal.openDialog to call setFormData(...) to the same
initial/empty shape used when the component mounts (match the snapshot clearing
behavior of ReferCommunityProjectModal.openDialog and
SubmitProjectModal.openDialog) so the modal always opens with a fresh form;
reference the formData state and setFormData setter when adding this reset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/projects/RepoLanguages.tsx`:
- Around line 56-69: The effect doesn't reset the loading state when repos
changes; update the useEffect for repoIds/repos so it calls setLoading(true) at
the start (and optionally setLanguages(undefined|null) to clear prior results)
before initiating the fetch, and use an AbortController to cancel the in-flight
fetch instead of a cancelled flag; make changes around the useEffect block that
references repoIds, repos, setLoading and setLanguages so refetches show the
skeleton properly.

In `@components/sections/FishBackground.tsx`:
- Line 3: The FishBackground component currently seeds its SVG layout using
Math.random() inside useState during initialization (the initializer that
creates the fish layout and count), which causes SSR/client hydration mismatches
and also freezes layout when the count prop changes; replace the
nondeterministic initializer by either (a) deriving a deterministic seeded RNG
from the count prop and using that to generate the layout in the component
render (or in a useMemo keyed by count) so server and client produce identical
output, or (b) move the component to client-only rendering via dynamic
import/Suspense and keep the random generation on mount; update the
layout-generation function reference (the function that currently calls
Math.random()) to accept a seed or to be invoked inside a client-only effect so
it no longer runs during SSR.

In `@package.json`:
- Line 30: Update the eslint dependency version range in package.json to require
at least v9.22.0 so that the named exports used in eslint.config.mjs
(defineConfig, globalIgnores) are present; specifically, change the "eslint"
entry currently set to "^9.0.0" to "^9.22.0" to prevent installs that resolve to
9.0.x–9.21.x where those exports are missing.

---

Outside diff comments:
In `@components/sections/ReferLocalGroupModal.tsx`:
- Around line 21-27: openDialog in ReferLocalGroupModal currently resets status,
verificationCode, codeError and openedAtRef but does not reset formData, causing
stale values to reappear via defaultValue bindings (formData.*). Update
ReferLocalGroupModal.openDialog to call setFormData(...) to the same
initial/empty shape used when the component mounts (match the snapshot clearing
behavior of ReferCommunityProjectModal.openDialog and
SubmitProjectModal.openDialog) so the modal always opens with a fresh form;
reference the formData state and setFormData setter when adding this reset.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e871471f-4b8e-46a7-8fe7-666acc33376b

📥 Commits

Reviewing files that changed from the base of the PR and between 908cc24 and 68f76bf.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • app/[lang]/layout.tsx
  • app/api/revalidate/route.ts
  • app/api/stats/route.ts
  • components/blog/ShareButtons.tsx
  • components/projects/RepoLanguages.tsx
  • components/sections/FishBackground.tsx
  • components/sections/HeroBanner.tsx
  • components/sections/ReferCommunityProjectModal.tsx
  • components/sections/ReferLocalGroupModal.tsx
  • components/sections/SubmitProjectModal.tsx
  • eslint.config.mjs
  • package.json
  • proxy.ts
  • src/i18n/request.ts
  • tsconfig.json

Comment thread components/projects/RepoLanguages.tsx Outdated
Comment thread components/sections/FishBackground.tsx Outdated
Comment thread package.json Outdated
JohnRDOrazio and others added 8 commits April 18, 2026 02:11
Replace imperative loading/languages reset with derived loading state
based on fetchedKey comparison. Use AbortController instead of a
cancelled flag for proper fetch cancellation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Math.random() with a mulberry32 seeded PRNG derived from the
count prop. This ensures server and client produce identical fish
layouts (no hydration mismatch) and the layout updates when count
changes via useMemo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The eslint.config.mjs uses defineConfig and globalIgnores which were
added in ESLint 9.22.0. Bump the minimum version to prevent installs
that resolve to older 9.x versions where these exports are missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add setFormData({}) to openDialog to clear stale form values,
matching the reset behavior of the other two modal components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Uncontrolled inputs with defaultValue only read the value on mount.
Add a formKey counter that increments on openDialog and handleBackToForm,
used as key on the <form> element to force React to remount inputs
with current formData values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…attribute

Codacy flags passing an async function directly to onSubmit since the
attribute expects void, not Promise<void>. Wrap in a non-async arrow
function that discards the return value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/sections/ReferCommunityProjectModal.tsx (1)

148-153: ⚠️ Potential issue | 🟡 Minor

Minor: handleBackToForm leaves tags state untouched — confirm this is intentional.

Unlike openDialog, handleBackToForm does not reset tags/tagInput. This is likely desired (preserve user-entered tags on back-navigation, mirroring how defaultValue re-populates text fields from formData.fields), but note that tags is not remounted by formKey since it lives outside the form subtree, so whatever the user last had in the chip input remains. If the intent was to restore the tags that were submitted (i.e. formData.tags) rather than the current live tags, you'd want setTags(formData.tags) here. As-is it's consistent — just flagging for awareness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/sections/ReferCommunityProjectModal.tsx` around lines 148 - 153,
handleBackToForm currently resets status, verificationCode, codeError, and
formKey but intentionally leaves tags/tagInput unchanged; decide whether to
preserve user-entered chips or restore the submitted tags and implement
accordingly: if you want to restore submitted tags use setTags(formData.tags)
(and optionally setTagInput('') to clear the input) inside handleBackToForm,
otherwise add a short comment in handleBackToForm noting tags/tagInput are
intentionally preserved to avoid confusion when future maintainers see the
discrepancy with openDialog.
🧹 Nitpick comments (2)
components/sections/FishBackground.tsx (2)

1-3: 'use client' may no longer be required.

Now that fish generation is fully deterministic and uses only useMemo (which works in Server Components as a no-op-friendly hook only when state is involved — and here there's no state, effects, refs, or browser APIs), this component could likely be a Server Component. Dropping 'use client' would shrink the client bundle and let the SVG markup ship as static RSC payload.

That said, if FishBackground is imported into other Client Components, the directive is harmless — feel free to skip if the cost/benefit isn't worth it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/sections/FishBackground.tsx` around lines 1 - 3, Remove the
top-level 'use client' directive from the FishBackground component so it can be
a Server Component and avoid shipping unnecessary client JS; locate the
FishBackground component in components/sections/FishBackground.tsx (which
currently imports useMemo) and confirm the deterministic fish generation only
uses useMemo with no state, effects, refs, or browser APIs, then delete the
"'use client'" line and ensure imports/exports remain unchanged so the SVG
markup is emitted as static RSC payload (keep 'use client' only if this
component is directly imported by other Client Components).

55-102: Deterministic seeding resolves the prior hydration concern.

The mulberry32 PRNG keyed off count produces identical output on server and client, and useMemo([count]) keeps the layout stable while still recomputing if count changes. Nicely addresses the previous Math.random feedback.

One small caveat worth being aware of: every <FishBackground count={N} /> rendered with the same count will now produce the exact same layout (same positions/rotations/path picks). If you ever render multiple instances on a page and want them to look distinct, consider accepting an optional seed prop (defaulting to count * 7919) so callers can vary it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/sections/FishBackground.tsx` around lines 55 - 102, The current
deterministic PRNG uses count*7919 so identical FishBackground instances share
layout; add an optional seed prop to FishBackground (e.g., function signature
FishBackground({ count = 5, seed?: number }) and prop type) defaulting to count
* 7919, use that seed when creating the RNG (replace createSeededRng(count *
7919) with createSeededRng(seed ?? count * 7919)), and include seed in the
useMemo dependency array ([count, seed]) so different seeds produce distinct
placements; update any callers/tests as needed to pass a custom seed when
uniqueness is required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@components/sections/ReferCommunityProjectModal.tsx`:
- Around line 148-153: handleBackToForm currently resets status,
verificationCode, codeError, and formKey but intentionally leaves tags/tagInput
unchanged; decide whether to preserve user-entered chips or restore the
submitted tags and implement accordingly: if you want to restore submitted tags
use setTags(formData.tags) (and optionally setTagInput('') to clear the input)
inside handleBackToForm, otherwise add a short comment in handleBackToForm
noting tags/tagInput are intentionally preserved to avoid confusion when future
maintainers see the discrepancy with openDialog.

---

Nitpick comments:
In `@components/sections/FishBackground.tsx`:
- Around line 1-3: Remove the top-level 'use client' directive from the
FishBackground component so it can be a Server Component and avoid shipping
unnecessary client JS; locate the FishBackground component in
components/sections/FishBackground.tsx (which currently imports useMemo) and
confirm the deterministic fish generation only uses useMemo with no state,
effects, refs, or browser APIs, then delete the "'use client'" line and ensure
imports/exports remain unchanged so the SVG markup is emitted as static RSC
payload (keep 'use client' only if this component is directly imported by other
Client Components).
- Around line 55-102: The current deterministic PRNG uses count*7919 so
identical FishBackground instances share layout; add an optional seed prop to
FishBackground (e.g., function signature FishBackground({ count = 5, seed?:
number }) and prop type) defaulting to count * 7919, use that seed when creating
the RNG (replace createSeededRng(count * 7919) with createSeededRng(seed ??
count * 7919)), and include seed in the useMemo dependency array ([count, seed])
so different seeds produce distinct placements; update any callers/tests as
needed to pass a custom seed when uniqueness is required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2357e213-9acf-4940-8087-21a8885cd85d

📥 Commits

Reviewing files that changed from the base of the PR and between 68f76bf and 1d07d86.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • components/projects/RepoLanguages.tsx
  • components/sections/FishBackground.tsx
  • components/sections/ReferCommunityProjectModal.tsx
  • components/sections/ReferLocalGroupModal.tsx
  • components/sections/SubmitProjectModal.tsx
  • package.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • package.json
  • components/sections/ReferLocalGroupModal.tsx
  • components/sections/SubmitProjectModal.tsx
  • components/projects/RepoLanguages.tsx

JohnRDOrazio and others added 4 commits April 18, 2026 02:48
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove 'use client' and useMemo — the deterministic seeded RNG needs
no hooks, so the component can render as a server component. Add an
optional seed prop for distinct layouts when multiple instances are
needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JohnRDOrazio JohnRDOrazio merged commit 4945623 into main Apr 18, 2026
8 checks passed
@JohnRDOrazio JohnRDOrazio deleted the feature/nextjs-16-upgrade branch April 18, 2026 01:08
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.

Upgrade to Next.js 16 and next-intl 4.9.1

1 participant