Skip to content

TEL-285: react compiler, server components, react 19, next 16#38

Open
burtonjong wants to merge 5 commits intomainfrom
TEL-285
Open

TEL-285: react compiler, server components, react 19, next 16#38
burtonjong wants to merge 5 commits intomainfrom
TEL-285

Conversation

@burtonjong
Copy link
Member

@burtonjong burtonjong commented Jan 31, 2026

Summary by CodeRabbit

  • New Features

    • Enabled React Compiler optimization for enhanced performance
  • Improvements

    • Upgraded to React 19 and Next.js 16 with latest security patches
    • Enhanced navigation scrolling detection for smoother page navigation
    • Reorganized team page layout with better section visibility
    • Streamlined recruitment forms interface
  • Refactoring

    • Optimized data loading architecture across pages
    • Removed unnecessary loading indicators for cleaner user experience
  • Chores

    • Updated core frameworks and dependencies

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 31, 2026

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

Project Deployment Actions Updated (UTC)
solar-car-website-next Ready Ready Preview, Comment Jan 31, 2026 11:07pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Dependency & Build Configuration
package.json, next.config.js, tsconfig.json
Updated React/Next.js major versions (React 18→19, Next.js 15→16), added babel-plugin-react-compiler, adjusted TS jsx setting to "react-jsx", and enabled reactCompiler in Next.js config.
Client Component Directives
src/app/_components/Buttons/BasicButton.tsx, src/app/_components/OurWork/YearSection.tsx
Added "use client" directive to enable client-side component context without functional logic changes.
Component Prop Refactoring
src/app/_components/Cars/CarScreenView.tsx
Removed handleImageLoad prop and onLoad handler from Image component, simplifying the component's public API.
PageBullets State Internalization
src/app/_components/Pagebullets/index.tsx
Converted from externally-managed state to internal state with useIntersectionObserver hook; renamed currentId prop to defaultCurrentId and removed handleDotClick prop in favor of internal useCallback handler.
Server Component Migrations & Loader Removals
src/app/our-work/page.tsx, src/app/sponsors/page.tsx, src/app/cars/page.tsx, src/app/loading.tsx, src/app/recruitment/loading.tsx, src/app/sponsors/loading.tsx
Migrated multiple page components from client to server components using async/await with TRPC server calls; removed all global and route-specific Loader components and related loading state.
Recruitment Component Extraction
src/app/_components/Recruitment/RecruitmentForms.tsx, src/app/_components/Recruitment/index.module.scss, src/app/recruitment/index.module.scss, src/app/recruitment/page.tsx
Introduced new RecruitmentForms server component to encapsulate form fetching and rendering; added corresponding SCSS module; simplified recruitment page to delegate to new component; removed old inline SCSS rules.
Homepage & Support Loading Simplification
src/app/page.tsx, src/app/support-us/page.tsx
Removed image loading state, handleImageLoad callback, and Loader usage; simplified exports by removing memo wrapping.
Intersection Observer Hook Enhancement
src/app/_hooks/useIntersectionObserver.ts
Refactored to track intersecting elements, prioritize topmost visible element, add fallback to first item, and improve cleanup; removed generic type parameter default.
Car Page Data Extraction
src/app/cars/carInformation.ts, src/app/cars/page.tsx
Created new carInformation.ts module exporting pageIds constant with car metadata; refactored cars/page.tsx to use external data and PageBulletsClientWrapper.
Team Page Data-Driven Restructuring
src/app/team/page.tsx
Replaced tempFlag conditional with comprehensive data-driven rendering of multiple team sections (Co-Chairs, Team Leads, Accounting, Communications, Sponsorship, Software, Electrical, Mechanical, Alumni) using dynamic team arrays.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • added co chairs on team page #24: Directly addresses Co-Chairs/Co-Chairs section modifications on the team page, overlapping with this PR's team page restructuring.

Suggested reviewers

  • justin-phxm

Poem

🐰 React springs forth at nineteen's call,
Compiler magic optimizes all,
Server-side dreams now fetch and render,
Loaders vanish, state winds down tender,
Teams assembled, pixels bright—
A furry hop toward the light! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: upgrading to React 19, Next 16, enabling React Compiler, and refactoring to server components.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch TEL-285

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.

Copy link

@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: 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 | 🟠 Major

Incorrect 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 | 🟠 Major

Recruitment close date is stale and conflicts with the UI copy.

RELEASE_DATE is August 23, 2025, while the banner says “Applications close on September 13th.” As of January 31, 2026, isRecruitmentClosed will always be true, 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: commmunicationsTeam has 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: Type pageIds to avoid downstream casts.
A lightweight value type keeps position strongly typed and avoids the as "left" | "right" cast in src/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 handle document.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: Drop memo around the async server component.
memo doesn’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 when isRecruitmentClosed is 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();

Comment on lines +23 to +28
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!`,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +43 to +51
<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>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
<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.

Copy link

@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: 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.

Copy link

@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: 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.

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.

2 participants