-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fixed intermittent React hydration crash on macOS when changing respo… #4862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
We require all PRs to follow Conventional Commits specification. |
📝 WalkthroughWalkthroughThese changes suppress React hydration warnings across three date/time-rendering components by adding Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
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 |
✅ Deploy Preview for asyncapi-website ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-4862--asyncapi-website.netlify.app/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (1)
components/campaigns/AnnouncementRemainingDays.tsx (1)
14-35: Consider client-only rendering for truly dynamic countdown.Unlike static event dates, this component calculates relative time (days/hours/minutes until the event), which genuinely differs between server build time and client page load time. While
suppressHydrationWarningis more justifiable here than for static dates, the server-rendered countdown will be stale by the time users see it.A better approach is to defer rendering to the client so the countdown reflects the actual current time:
🔎 Recommended: Client-only rendering
export default function AnnouncementRemainingDays({ dateTime, eventName }: AnnouncementRemainingDaysProps) { + const [text, setText] = React.useState(''); + + React.useEffect(() => { const date = moment(dateTime); const now = moment(); const days = date.diff(now, 'days'); const hours = date.diff(now, 'hours'); const minutes = date.diff(now, 'minutes'); - let text = ''; + let countdownText = ''; if (days >= 1) { - text = `${days} ${days === 1 ? 'day' : 'days'}`; + countdownText = `${days} ${days === 1 ? 'day' : 'days'}`; } else if (hours > 1) { - text = 'A few hours'; + countdownText = 'A few hours'; } else if (minutes > 1) { - text = 'A few minutes'; + countdownText = 'A few minutes'; } + + setText(countdownText); + }, [dateTime]); return ( - <span className='countdown-text-gradient block font-extrabold' data-testid='AnnouncementRemainingDays-text' suppressHydrationWarning> + <span className='countdown-text-gradient block font-extrabold' data-testid='AnnouncementRemainingDays-text'> {text} until {eventName} </span> );This ensures the countdown displays the correct time when the page loads in the browser, rather than a stale server-rendered value.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
components/Calendar.tsxcomponents/campaigns/AnnouncementRemainingDays.tsxcomponents/navigation/EventPostItem.tsx
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-12-29T14:21:28.216Z
Learnt from: sammy200-ui
Repo: asyncapi/website PR: 4804
File: components/navigation/Filter.tsx:32-41
Timestamp: 2025-12-29T14:21:28.216Z
Learning: In the asyncapi/website repository, when you intentionally omit dependencies in React hooks (e.g., useEffect, useMemo), add an eslint-disable comment with an explanatory note above the line to justify the design choice. For example: // eslint-disable-next-line react-hooks/exhaustive-deps: intentionally omitting dependencies to avoid unnecessary re-runs. This helps reviewers understand the rationale and keeps lint guidance informative.
Applied to files:
components/campaigns/AnnouncementRemainingDays.tsxcomponents/Calendar.tsxcomponents/navigation/EventPostItem.tsx
📚 Learning: 2024-10-11T07:38:35.745Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/newsroom/FeaturedBlogPost.tsx:90-92
Timestamp: 2024-10-11T07:38:35.745Z
Learning: In Next.js, nested `<a>` tags cause hydration issues due to invalid HTML. To fix this, avoid nesting `<a>` tags by replacing inner `<a>` tags with non-interactive elements like `<span>`, and use click handlers to maintain interactivity if needed.
Applied to files:
components/Calendar.tsx
📚 Learning: 2024-10-11T11:32:30.226Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/navigation/BlogPostItem.tsx:95-119
Timestamp: 2024-10-11T11:32:30.226Z
Learning: In the `BlogPostItem` component (`components/navigation/BlogPostItem.tsx`), nesting `<a>` tags inside the parent `Link` component leads to hydration issues; therefore, we should avoid nesting `<a>` tags inside `Link` components in this component.
Applied to files:
components/Calendar.tsxcomponents/navigation/EventPostItem.tsx
📚 Learning: 2024-10-11T10:46:58.882Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/Avatar.tsx:35-44
Timestamp: 2024-10-11T10:46:58.882Z
Learning: In Next.js, when avoiding nested `<a>` tags due to hydration issues, use accessible non-link elements like `<button>` or `<span>` with appropriate roles, `tabIndex`, and event handlers to maintain accessibility and SEO.
Applied to files:
components/Calendar.tsx
📚 Learning: 2024-10-11T11:17:32.246Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/navigation/BlogPostItem.tsx:80-87
Timestamp: 2024-10-11T11:17:32.246Z
Learning: In the `BlogPostItem` component, the parent `Link` wraps the entire content, so inner `Link` components around the title and excerpt are unnecessary.
Applied to files:
components/navigation/EventPostItem.tsx
📚 Learning: 2025-12-23T06:30:43.275Z
Learnt from: katara-Jayprakash
Repo: asyncapi/website PR: 4760
File: components/layout/GenericPostLayout.tsx:50-52
Timestamp: 2025-12-23T06:30:43.275Z
Learning: In the asyncapi/website repository, `GenericPostLayout` (components/layout/GenericPostLayout.tsx) is used for rendering about pages, not blog posts, even though it accepts a post typed as `IPosts['blog'][number]`. The type is reused for convenience. Blog posts use `BlogLayout.tsx` instead. When reviewing EditPageButton usage in GenericPostLayout, `contentType='about'` is the correct value.
Applied to files:
components/navigation/EventPostItem.tsx
📚 Learning: 2025-06-03T06:48:09.304Z
Learnt from: iambami
Repo: asyncapi/website PR: 4111
File: markdown/blog/community-spotlight-aayush.md:3-3
Timestamp: 2025-06-03T06:48:09.304Z
Learning: For AsyncAPI community spotlight blog posts, the frontmatter date should reflect the actual publication date, not the month being highlighted in the spotlight. For example, a "May Community Spotlight" can be published in June with a June date in the frontmatter.
Applied to files:
components/navigation/EventPostItem.tsx
🪛 GitHub Actions: PR testing - if Node project
components/campaigns/AnnouncementRemainingDays.tsx
[error] 32-32: Prettier formatting issue: Replace spacing around elements as suggested.
components/Calendar.tsx
[error] 49-49: Prettier formatting issue: Replace {moment(event.date).format('D')} with a multi-line version as suggested.
components/navigation/EventPostItem.tsx
[error] 53-54: Expected blank line after variable declarations and before following statement.
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Redirect rules - asyncapi-website
- GitHub Check: Header rules - asyncapi-website
- GitHub Check: Pages changed - asyncapi-website
🔇 Additional comments (1)
components/navigation/EventPostItem.tsx (1)
49-58: Variable reordering looks correct.Moving
postDateinitialization before theactivecheck ensures the date is available for validity checking and comparison. The logic correctly handles invalid dates and past events.
| <a href={event.url} className='mb-1 mt-2 flex grow flex-col items-start sm:flex-row sm:items-center'> | ||
| <div className='inline-flex h-12 min-w-12 flex-row rounded-full bg-pink-500 font-bold text-white'> | ||
| <span className='flex-1 self-center text-center'>{moment(event.date).format('D')}</span> | ||
| <span className='flex-1 self-center text-center' suppressHydrationWarning>{moment(event.date).format('D')}</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Prettier formatting issue.
The pipeline reports a formatting error on this line. Run npm run format or yarn format to apply the required formatting.
🧰 Tools
🪛 GitHub Actions: PR testing - if Node project
[error] 49-49: Prettier formatting issue: Replace {moment(event.date).format('D')} with a multi-line version as suggested.
🤖 Prompt for AI Agents
In components/Calendar.tsx around line 49, the line with the span element fails
Prettier formatting; run the project's formatter (npm run format or yarn format)
or apply Prettier rules to reformat this line (adjust spacing/quotes/className
placement as Prettier requires) and then stage the updated file so the pipeline
passes.
| <span className='flex-1 self-center text-center' suppressHydrationWarning>{moment(event.date).format('D')}</span> | ||
| </div> | ||
| <div className='grow text-left sm:mt-0 sm:pl-6'> | ||
| <h2 className='title-font font-medium text-gray-900 hover:text-gray-500'>{event.title}</h2> | ||
| <p className='text-gray-600'> | ||
| <p className='text-gray-600' suppressHydrationWarning> | ||
| {moment(event.date).local().format('LLLL')} UTC | ||
| {moment(event.date).local().format('Z')} | ||
| </p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suppressHydrationWarning masks the problem rather than fixing it.
These event dates are static data from meetings.json and should render identically on both server and client. The hydration mismatch occurs because line 54 calls .local(), which applies the user's local timezone—causing the server-rendered UTC output to differ from the client-rendered localized output.
Instead of suppressing the warning, fix the root cause by:
- Rendering dates in a fixed timezone (UTC) on both server and client, or
- Deferring date rendering to client-only using
useEffect+useState.
React documentation states that suppressHydrationWarning should only be used for content that is intentionally different between server and client (e.g., real-time timestamps). Static event dates don't qualify.
🔎 Recommended fix: Remove `.local()` for consistent UTC rendering
- <span className='flex-1 self-center text-center' suppressHydrationWarning>{moment(event.date).format('D')}</span>
+ <span className='flex-1 self-center text-center'>{moment(event.date).utc().format('D')}</span>
</div>
<div className='grow text-left sm:mt-0 sm:pl-6'>
<h2 className='title-font font-medium text-gray-900 hover:text-gray-500'>{event.title}</h2>
- <p className='text-gray-600' suppressHydrationWarning>
- {moment(event.date).local().format('LLLL')} UTC
- {moment(event.date).local().format('Z')}
+ <p className='text-gray-600'>
+ {moment(event.date).utc().format('LLLL')} UTC
</p>This ensures both server and client render the same UTC date, eliminating the hydration mismatch.
Alternative: Client-only rendering
If you want to show dates in the user's local timezone, defer rendering to the client:
+const [formattedDates, setFormattedDates] = React.useState<Map<number, {day: string, full: string}>>(new Map());
+
+React.useEffect(() => {
+ const dates = new Map();
+ getEvents(eventsData, size).forEach((event, index) => {
+ dates.set(index, {
+ day: moment(event.date).format('D'),
+ full: `${moment(event.date).local().format('LLLL')} UTC${moment(event.date).local().format('Z')}`
+ });
+ });
+ setFormattedDates(dates);
+}, [size]);Then use formattedDates.get(index)?.day and formattedDates.get(index)?.full in the JSX.
📝 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.
| <span className='flex-1 self-center text-center' suppressHydrationWarning>{moment(event.date).format('D')}</span> | |
| </div> | |
| <div className='grow text-left sm:mt-0 sm:pl-6'> | |
| <h2 className='title-font font-medium text-gray-900 hover:text-gray-500'>{event.title}</h2> | |
| <p className='text-gray-600'> | |
| <p className='text-gray-600' suppressHydrationWarning> | |
| {moment(event.date).local().format('LLLL')} UTC | |
| {moment(event.date).local().format('Z')} | |
| </p> | |
| <span className='flex-1 self-center text-center'>{moment(event.date).utc().format('D')}</span> | |
| </div> | |
| <div className='grow text-left sm:mt-0 sm:pl-6'> | |
| <h2 className='title-font font-medium text-gray-900 hover:text-gray-500'>{event.title}</h2> | |
| <p className='text-gray-600'> | |
| {moment(event.date).utc().format('LLLL')} UTC | |
| </p> |
🧰 Tools
🪛 GitHub Actions: PR testing - if Node project
[error] 49-49: Prettier formatting issue: Replace {moment(event.date).format('D')} with a multi-line version as suggested.
🤖 Prompt for AI Agents
In components/Calendar.tsx around lines 49–56 the hydration mismatch is caused
by using .local() (and suppressHydrationWarning) so server renders UTC while
client renders local time; fix by removing suppressHydrationWarning and
rendering dates in a fixed timezone on both server and client (e.g., format
using UTC via moment.utc(event.date).format(...) or otherwise ensure you call
.utc() instead of .local()), or if you prefer local-time display, defer
formatting to the client by moving date formatting into a useEffect that sets
formatted strings in state and render those client-only values; choose one
approach and remove suppressHydrationWarning so server and client output match.
|
|
||
| return ( | ||
| <span className='countdown-text-gradient block font-extrabold' data-testid='AnnouncementRemainingDays-text'> | ||
| <span className='countdown-text-gradient block font-extrabold' data-testid='AnnouncementRemainingDays-text' suppressHydrationWarning> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Prettier formatting issue.
The pipeline reports a formatting error on this line. Run npm run format or yarn format to apply the required spacing corrections.
🧰 Tools
🪛 GitHub Actions: PR testing - if Node project
[error] 32-32: Prettier formatting issue: Replace spacing around elements as suggested.
🤖 Prompt for AI Agents
In components/campaigns/AnnouncementRemainingDays.tsx around line 32, Prettier
flagged a formatting issue on the span tag; run the project formatter (npm run
format or yarn format) to apply the required spacing/format corrections, or
manually reformat the JSX tag to match the project's Prettier settings (correct
spacing between attributes and close the tag consistently) and then re-run the
formatter or commit the formatted file.
| let active = true; | ||
| if (!postDate.isValid()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Prettier/ESLint formatting issue.
The pipeline reports a missing blank line after the variable declarations on line 53. Add a blank line between the let active = true; declaration and the following if statement.
🔎 Proposed fix
const postDate = moment(post.date);
let active = true;
+
if (!postDate.isValid()) {📝 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.
| let active = true; | |
| if (!postDate.isValid()) { | |
| const postDate = moment(post.date); | |
| let active = true; | |
| if (!postDate.isValid()) { |
🧰 Tools
🪛 GitHub Actions: PR testing - if Node project
[error] 53-54: Expected blank line after variable declarations and before following statement.
🤖 Prompt for AI Agents
In components/navigation/EventPostItem.tsx around lines 53 to 54, add a blank
line after the variable declaration "let active = true;" so there's an empty
line before the subsequent "if (!postDate.isValid())" statement to satisfy
Prettier/ESLint formatting rules; update the file accordingly and re-run the
formatter/linter to confirm the warning is resolved.
| <span className='ml-4 text-sm font-semibold' data-testid='Event-span' suppressHydrationWarning> | ||
| {active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'} | ||
| </span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suppressHydrationWarning masks the problem for static event dates.
The event date from post.date is static data and should render identically on both server and client. The hydration mismatch likely stems from locale-dependent formatting ('MMMM D, YYYY') or timezone handling in moment().
Instead of suppressing the warning, ensure consistent rendering:
- Use UTC consistently:
moment(postDate).utc().format('MMMM D, YYYY') - Lock locale: Configure moment to use a fixed locale on both server and client
- Client-only rendering: Defer to
useEffectif you need user-localized dates
Static event dates don't qualify for suppressHydrationWarning per React's guidance—this attribute is intended for intentionally mismatched content like real-time timestamps.
🔎 Recommended fix: Use UTC for consistent rendering
- <span className='ml-4 text-sm font-semibold' data-testid='Event-span' suppressHydrationWarning>
+ <span className='ml-4 text-sm font-semibold' data-testid='Event-span'>
- {active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'}
+ {active ? moment(postDate).utc().format('MMMM D, YYYY') : 'View Recording'}
</span>📝 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.
| <span className='ml-4 text-sm font-semibold' data-testid='Event-span' suppressHydrationWarning> | |
| {active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'} | |
| </span> | |
| <span className='ml-4 text-sm font-semibold' data-testid='Event-span'> | |
| {active ? moment(postDate).utc().format('MMMM D, YYYY') : 'View Recording'} | |
| </span> |
🤖 Prompt for AI Agents
In components/navigation/EventPostItem.tsx around lines 82–84, the span uses
suppressHydrationWarning to hide a server/client mismatch caused by locale or
timezone differences when formatting postDate with moment; remove
suppressHydrationWarning and ensure deterministic rendering by formatting the
date in UTC and a fixed locale on both server and client (e.g., call
moment(postDate).utc().locale('en').format('MMMM D, YYYY')), or if you need
user-localized output defer formatting to the client via useEffect and render a
stable placeholder on the server.
Description
Problem
The website intermittently crashed with React production errors:
Error #425
Error #418
Error #423
This occurred more frequently when:
Switching responsive viewports in DevTools (e.g. iPhone XR, iPhone 14 Pro Max, iPhone SE)
Using a non-UTC timezone (e.g. IST – UTC+5:30)
Using non-US locale or date formatting
Running on macOS
These conditions caused differences between server-rendered HTML and client-rendered output, resulting in hydration failures.
Root Cause
Viewport-dependent logic ran during SSR, producing markup that differed from the client render.
Locale and timezone-dependent formatting (dates, numbers) produced inconsistent output between server and client.
React hydration failed when DevTools device emulation or user locale differed from the server environment.
✅ Solution
This PR ensures deterministic server rendering by:
Deferring viewport-specific logic to the client side only
Making locale- and timezone-dependent rendering consistent between SSR and CSR
Preventing hydration mismatches by aligning server and client output
As a result, hydration now succeeds reliably across:
All screen sizes
Device emulation modes
Timezones
Locales
Operating systems (including macOS)
Tested On
macOS (Chrome, Safari)
Multiple responsive device presets
Non-UTC timezones (IST)
Non-US locale/date formats
Related issue(s)
Fixes #4858
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.