TEL-285: react compiler, server components, react 19, next 16#38
TEL-285: react compiler, server components, react 19, next 16#38burtonjong wants to merge 5 commits intomainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR upgrades React from 18 to 19 and Next.js from 15 to 16, enables React Compiler, migrates multiple page components from client to server, removes loading components across routes, refactors component props and state management, and restructures team and recruitment rendering. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/team/page.tsx (1)
1-1:⚠️ Potential issue | 🟠 MajorIncorrect directive:
"use server"is for Server Actions, not Server Components.In Next.js App Router, components are Server Components by default. The
"use server"directive is specifically for marking functions as Server Actions (callable from the client), not for designating a file as a Server Component. This directive here is either unnecessary or incorrect.Remove this line entirely—the component will still be a Server Component.
Proposed fix
-"use server"; - import Footer from "@/app/_components/Footer";src/app/recruitment/page.tsx (1)
10-22:⚠️ Potential issue | 🟠 MajorRecruitment close date is stale and conflicts with the UI copy.
RELEASE_DATEis August 23, 2025, while the banner says “Applications close on September 13th.” As of January 31, 2026,isRecruitmentClosedwill always betrue, so the close-date banner never renders and forms stay hidden. Please align the actual close date and the copy, ideally from a single source of truth.🛠️ Suggested fix (single source of truth for close date)
- const RELEASE_DATE = new Date("2025-08-23T00:00:00"); - const isRecruitmentClosed = RELEASE_DATE < new Date(); + const APPLICATION_CLOSE_DATE = new Date("2026-09-13T00:00:00Z"); // update to actual close date + const isRecruitmentClosed = new Date() >= APPLICATION_CLOSE_DATE; + const closeDateLabel = APPLICATION_CLOSE_DATE.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); ... - <div className={styles.applicationDescription}> - Applications close on September 13th. - </div> + <div className={styles.applicationDescription}> + Applications close on {closeDateLabel}. + </div>
🤖 Fix all issues with AI agents
In `@next.config.js`:
- Around line 36-38: The keys in next.config.js are out of alphabetical order
and trigger the sort-keys ESLint rule; reorder the object keys so they are in
ascending alphabetical order (place reactCompiler before reactStrictMode and
transpilePackages as needed) by updating the config object entries
(reactStrictMode, transpilePackages, reactCompiler) to the correct alphabetical
order to satisfy the linter.
- Line 38: The Next.js config violates eslint-plugin-sort-keys ordering; move
the reactCompiler key so keys are alphabetized (place reactCompiler before
reactStrictMode) in the exported config object that contains images,
reactCompiler, reactStrictMode, and transpilePackages; update the object key
order to images, reactCompiler, reactStrictMode, transpilePackages to satisfy
the rule without changing values or behavior.
In `@package.json`:
- Around line 38-39: package.json lists "react" and "react-dom" at 19.2.4 but
the corresponding type packages are still on 18.x; update the `@types` packages to
a compatible 19.x version. Edit package.json to set "@types/react" and
"@types/react-dom" to "^19.0.0" (or the latest 19.x), then run your package
manager (npm/yarn/pnpm) to reinstall and update the lockfile; verify no type
errors and adjust import/use sites if any breaking type changes surface.
In `@src/app/_components/Recruitment/index.module.scss`:
- Around line 57-65: The media query using (max-height: 768px) is overriding the
smaller .pageHeading font-size set for narrow screens; update the rule so it
only applies on wider viewports (e.g. add a min-width constraint) or exclude
small widths—modify the media query that contains .container and .pageHeading to
something like (min-width: 641px) and (max-height: 768px) or otherwise gate the
.pageHeading font-size change so the .pageHeading selector does not override the
small-screen font-size.
In `@src/app/_components/Recruitment/RecruitmentForms.tsx`:
- Around line 37-41: The Link that renders external recruitment forms (Link with
href={form.link} and target="_blank" in RecruitmentForms.tsx) must include
rel="noopener noreferrer" to prevent reverse tabnabbing; update the Link
component props to add rel="noopener noreferrer" alongside target="_blank" for
all places where external links are opened (e.g., the Link wrapping form.link)
so it matches the pattern used in TeamMember/index.tsx.
In `@src/app/_hooks/useIntersectionObserver.ts`:
- Around line 13-31: The forEach callback currently uses an early `return` which
Biome flags; replace that with a guard that skips Next.js elements by negating
the check and wrapping the update logic. Specifically, inside the
entries.forEach callback (the callback that reads `const id = entry.target.id`),
remove the `return;` and change the conditional that checks `id === "__next" ||
id === "__next-build-watcher" || id === "locatorjs-wrapper" || id ===
"clerk-components"` into a single negated guard (e.g., `if (!(...)) { /* Update
tracking map: use intersectingElements.set/delete as before based on
entry.isIntersecting */ }`) so the code no longer returns from the callback.
In `@src/app/cars/carInformation.ts`:
- Around line 23-28: The string assigned to the content property in
carInformation.ts contains a grammar error and time‑sensitive tense; change
"it’s design phase" to "its design phase", update the sentence "In summer of
2025, Elysia will be retiring…" to past tense (e.g., "In summer 2025, Elysia
retired..." or "Elysia retired in summer 2025"), and either remove or replace
"Stay tuned for race updates!" with a past-tense or concluding line (e.g., note
the retirement result or that updates are archived) so the content is accurate
as of January 31, 2026.
In `@src/app/our-work/page.tsx`:
- Around line 17-21: The current render guard uses truthiness so an empty array
still mounts PageBulletsClient with empty pageIds and a bogus defaultCurrentId;
change the condition to check timelineData?.length > 0 before rendering
PageBulletsClient, pass pageIds={timelineData.map(d => d.year)} as before, and
set defaultCurrentId to timelineData[0]?.year (no hardcoded "2025") or omit
defaultCurrentId when timelineData is empty so PageBulletsClient is never
rendered with invalid defaults.
In `@src/app/team/page.tsx`:
- Around line 43-51: The Co-Chairs block unconditionally renders TeamMember with
engineeringTeamManager, teamCaptain, and businessTeamManager which may be
undefined; wrap each usage in null guards or conditionally render by filtering
so only defined users are passed to TeamMember (e.g., check
engineeringTeamManager, teamCaptain, businessTeamManager before rendering, or
build an array [engineeringTeamManager, teamCaptain,
businessTeamManager].filter(Boolean) and map to <TeamMember />) to prevent
passing undefined user props to TeamMember.
🧹 Nitpick comments (7)
src/app/team/page.tsx (2)
52-178: Consider extracting a reusable component to reduce duplication.All team sections (Subteam Managers, Team Leads, Accounting, Communications, etc.) follow an identical rendering pattern. This repetition increases maintenance burden and risk of inconsistencies.
Suggested helper component
// Could be defined at the top of the file or extracted to a separate component const TeamSection = ({ title, members, keyProp = "clerkUserId" }: { title: string; members: typeof managerRoles | typeof alumniTeam; keyProp?: "clerkUserId" | "id"; }) => { if (!members || members.length === 0) return null; return ( <div className={styles.teamRoleContainer}> <div className={styles.title}>{title}</div> <div className={styles.teamMembers}> {members.map((teamMember) => ( <TeamMember key={teamMember[keyProp]} user={teamMember} /> ))} </div> </div> ); }; // Usage: <TeamSection title="Subteam Managers" members={managerRoles} /> <TeamSection title="Team Leads" members={leadRoles} /> <TeamSection title="Alumni" members={alumniTeam} keyProp="id" /> // ... etc
91-102: Fix typo in variable name:commmunicationsTeamhas three 'm's instead of two.The typo is defined in the backend (
src/server/api/routers/fe.ts:190) and propagates through the API response to the frontend. All other team variables (accountingTeam, sponsorshipTeam, softwareTeam, electricalTeam, mechanicalTeam, multiTeam) use correct spelling. Fix the backend definition and update the frontend destructuring to improve code consistency and readability.src/app/cars/carInformation.ts (2)
8-12: TypepageIdsto avoid downstream casts.
A lightweight value type keepspositionstrongly typed and avoids theas "left" | "right"cast insrc/app/cars/page.tsx.♻️ Suggested typing
+import { type StaticImageData } from "next/image"; import backsplash1 from "public/assets/cars/backsplash1.png"; import backsplash2 from "public/assets/cars/backsplash2.jpeg"; import backsplash3 from "public/assets/cars/backsplash3.jpeg"; import backsplash4 from "public/assets/cars/backsplash4.jpeg"; import backsplash5 from "public/assets/cars/backsplash5.jpeg"; import backsplash6 from "public/assets/cars/backsplash6.jpeg"; -export const pageIds = { +type CarPage = { + content: string; + image: StaticImageData; + position: "left" | "right"; + title: string; +}; + +export const pageIds: Record<string, CarPage> = {
60-71: Consider renaming "Schulich 1" key to follow HTML id best practices.
While"Schulich 1"violates the HTML specification for id attributes (spaces are not recommended), modern browsers handledocument.getElementById()and scrolling with spaced ids without issues. The current implementation works functionally, but using a slug format (e.g.,"schulich-1") would align with web standards and be more robust. This is the only pageId key with a space among the six entries.package.json (1)
36-36: Keep eslint-config-next in sync with Next 16.
Aligning the lint config with the Next.js major version helps avoid rule mismatches and stale lint behavior.♻️ Suggested update
- "eslint-config-next": "15.5.9", + "eslint-config-next": "16.1.6",src/app/sponsors/page.tsx (1)
4-45: Dropmemoaround the async server component.
memodoesn’t provide value for server components and can be awkward with async components; exporting the async component directly is simpler.♻️ Suggested simplification
-import { memo } from "react"; +// memo removed const Sponsors = async () => { const sponsors = await trpc.fe.getSponsors(); ... }; -export default memo(Sponsors); +export default Sponsors;src/app/_components/Recruitment/RecruitmentForms.tsx (1)
8-14: Avoid fetching recruitment forms when recruitment is closed.
The TRPC call runs even whenisRecruitmentClosedis true; gating it avoids unnecessary work.♻️ Suggested change
export default async function RecruitmentForms({ isRecruitmentClosed, }: { isRecruitmentClosed: boolean; }) { - const recruitmentForms = await trpc.fe.getRecruitment(); + const recruitmentForms = isRecruitmentClosed + ? [] + : await trpc.fe.getRecruitment();
| content: `Schulich Elysia started it’s design phase in 2016 and this catamaran | ||
| style cruise car was completed in 2019. This design was implemented to improve | ||
| battery cooling as well as increase aerodynamics. The Elysia raced in the 2019 | ||
| American Solar Challenge where it took first place in the Multi Occupant Vehicle Class. | ||
| In summer of 2025, Elysia will be retiring at its final race at the Formula Sun Grand | ||
| Prix in Bowling Green, Kentucky. Stay tuned for race updates!`, |
There was a problem hiding this comment.
Refresh time‑sensitive copy and fix grammar.
As of January 31, 2026, “In summer of 2025, Elysia will be retiring…” is now in the past; please update the tense and consider removing the “Stay tuned” line if the event has already occurred. Also, “it’s design phase” should be “its design phase.”
🤖 Prompt for AI Agents
In `@src/app/cars/carInformation.ts` around lines 23 - 28, The string assigned to
the content property in carInformation.ts contains a grammar error and
time‑sensitive tense; change "it’s design phase" to "its design phase", update
the sentence "In summer of 2025, Elysia will be retiring…" to past tense (e.g.,
"In summer 2025, Elysia retired..." or "Elysia retired in summer 2025"), and
either remove or replace "Stay tuned for race updates!" with a past-tense or
concluding line (e.g., note the retirement result or that updates are archived)
so the content is accurate as of January 31, 2026.
| <div className={styles.teamMembersContainer}> | ||
| <div className={styles.teamRoleContainer}> | ||
| <div className={styles.title}>Co-Chairs</div> | ||
| <div className={styles.teamManagers}> | ||
| <TeamMember user={engineeringTeamManager} /> | ||
| <TeamMember user={teamCaptain} /> | ||
| <TeamMember user={businessTeamManager} /> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Co-Chairs section renders without null checks for potentially undefined values.
Unlike the other team sections that guard against undefined/empty arrays, the Co-Chairs section unconditionally renders TeamMember components with engineeringTeamManager, teamCaptain, and businessTeamManager. If any of these are undefined (e.g., data fetch fails or returns partial data), this could cause rendering issues or errors depending on how TeamMember handles undefined user props.
Proposed fix: Add null guards
<div className={styles.teamRoleContainer}>
<div className={styles.title}>Co-Chairs</div>
<div className={styles.teamManagers}>
- <TeamMember user={engineeringTeamManager} />
- <TeamMember user={teamCaptain} />
- <TeamMember user={businessTeamManager} />
+ {engineeringTeamManager && <TeamMember user={engineeringTeamManager} />}
+ {teamCaptain && <TeamMember user={teamCaptain} />}
+ {businessTeamManager && <TeamMember user={businessTeamManager} />}
</div>
</div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className={styles.teamMembersContainer}> | |
| <div className={styles.teamRoleContainer}> | |
| <div className={styles.title}>Co-Chairs</div> | |
| <div className={styles.teamManagers}> | |
| <TeamMember user={engineeringTeamManager} /> | |
| <TeamMember user={teamCaptain} /> | |
| <TeamMember user={businessTeamManager} /> | |
| </div> | |
| </div> | |
| <div className={styles.teamMembersContainer}> | |
| <div className={styles.teamRoleContainer}> | |
| <div className={styles.title}>Co-Chairs</div> | |
| <div className={styles.teamManagers}> | |
| {engineeringTeamManager && <TeamMember user={engineeringTeamManager} />} | |
| {teamCaptain && <TeamMember user={teamCaptain} />} | |
| {businessTeamManager && <TeamMember user={businessTeamManager} />} | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In `@src/app/team/page.tsx` around lines 43 - 51, The Co-Chairs block
unconditionally renders TeamMember with engineeringTeamManager, teamCaptain, and
businessTeamManager which may be undefined; wrap each usage in null guards or
conditionally render by filtering so only defined users are passed to TeamMember
(e.g., check engineeringTeamManager, teamCaptain, businessTeamManager before
rendering, or build an array [engineeringTeamManager, teamCaptain,
businessTeamManager].filter(Boolean) and map to <TeamMember />) to prevent
passing undefined user props to TeamMember.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/app/_hooks/useIntersectionObserver.ts`:
- Around line 73-75: Replace the manual per-element unobserve loop with a single
observer.disconnect() call for cleanup in the useIntersectionObserver hook: call
observer.disconnect() to release all observed targets and then clear
intersectingElements (keep the existing intersectingElements.clear()); update
the teardown in the return cleanup to reference the observer variable rather
than iterating over elements.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@package.json`:
- Line 59: Update the eslint-config-next dependency to match the Next.js
version: change the "eslint-config-next" entry in package.json from "15.5.9" to
"^16.1.6" (so it aligns with "next" ^16.1.6), then reinstall dependencies
(npm/yarn/pnpm) to update the lockfile and ensure ESLint flat config
compatibility.
In `@src/app/our-work/page.tsx`:
- Line 1: Remove the client-only memo usage: delete the import of memo from
"react" and remove the memo(...) wrapper around the async Server Component (the
exported component in this file, e.g., the default exported OurWork page
function), returning/exporting the async component directly; this removes the
semantically incorrect client optimization for a Server Component.
Summary by CodeRabbit
New Features
Improvements
Refactoring
Chores
✏️ Tip: You can customize this high-level summary in your review settings.