Skip to content

feat(ui): optimize component performance#54

Open
YueMiyuki wants to merge 3 commits intomainfrom
dev
Open

feat(ui): optimize component performance#54
YueMiyuki wants to merge 3 commits intomainfrom
dev

Conversation

@YueMiyuki
Copy link
Collaborator

@YueMiyuki YueMiyuki commented Mar 6, 2026


Summary by cubic

Improved UI performance and scroll behavior by reducing re-renders, initializing navbar scroll state correctly, and debouncing the scroll handler. Also fixed contact form rate limiting to only apply after successful webhook delivery, and refreshed site copy and metadata.

  • Performance & Fixes

    • Contact API: send Discord webhook first; update rate limit only on success.
    • DB: wrap getCollection with React.cache for per-request deduplication.
    • UI: hoisted static data, CSS-only hover/glitch effects with motion-reduce support, icon mapping by name, stabilized IntersectionObserver; debounced navbar scroll with a stable handler, cleanup, and initial state from window.
    • Transitions: use popLayout in AnimatePresence for smoother page changes.
  • Copy Updates

    • Updated site title/description and OG/Twitter metadata.
    • New hero headline, footer blurb, and contact text.

Written for commit 95564d5. Summary will update on new commits.

Summary by CodeRabbit

  • Updates

    • Refreshed site branding and metadata to "mdesk.tech - Web Design That Actually Works"
    • Updated hero headline, replaced stats with a mapped stats block, and removed the promotional badge
    • Revised contact wording to a concise project prompt with a 24‑hour response note
    • Updated footer messaging for clearer brand promise
    • Improved navbar hover/scroll behavior and smoother page-transition orchestration
    • Accessibility and motion-reduction refinements
  • Chores

    • Formatting and whitespace standardization across many files (minor cleanups)

@vercel
Copy link

vercel bot commented Mar 6, 2026

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

Project Deployment Actions Updated (UTC)
mdesk-tech Ready Ready Preview, Comment Mar 7, 2026 2:41am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c0c85b36-0a35-478c-982a-d02eeaa1c375

📥 Commits

Reviewing files that changed from the base of the PR and between b86973c and 95564d5.

📒 Files selected for processing (1)
  • components/Navbar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/Navbar.tsx

📝 Walkthrough

Walkthrough

Whitespace normalization across many files, updated site branding text/metadata, refactored Features icon handling to use string-to-component mapping, added React cache for DB collection retrieval, several UI/behavior tweaks (Navbar debounced scroll + CSS hover, PageTransition mode, LazySection memo removal, intersection observer stabilization), and narrowed lint-staged globs.

Changes

Cohort / File(s) Summary
Formatting (EOF/newline fixes)
app/about/layout.tsx, app/about/page.tsx, app/apple-icon.tsx, app/contact/page.tsx, app/icon.tsx, app/open-source/layout.tsx, app/open-source/page.tsx, app/opengraph-image.tsx, app/page.tsx, app/privacy/page.tsx, app/robots.ts, app/services/layout.tsx, app/services/page.tsx, app/services/seo/page.tsx, app/services/seo/seo-content.tsx, app/services/web-design/page.tsx, app/services/web-development/page.tsx, app/services/web-development/web-development-content.tsx, app/services/hosting/page.tsx, app/sitemap.ts, app/terms/page.tsx, app/twitter-image.tsx, components/About.tsx, app/contact/layout.tsx
Added/normalized trailing newlines and minor closing-brace whitespace; no behavioral changes.
Branding & Copy
app/layout.tsx, components/Footer.tsx, components/Hero.tsx, components/Contact.tsx
Updated metadata titles/descriptions and Open Graph/Twitter alt text to new branding; adjusted footer and hero copy and removed hero badge; updated Contact copy.
Icon Handling Refactor
components/Features.tsx
Replaced icon: React.ReactNode with iconName: IconName; added IconName type and iconMap (LucideIcon mapping); render icons via mapped components instead of passed JSX — public Feature interface changed.
UI Behavior & Animations
components/Navbar.tsx, components/PageTransition.tsx
Navbar: added internal debounced scroll handler, moved hover/glow behavior to CSS (removed hover state handlers). PageTransition: changed AnimatePresence mode from "wait" to "popLayout".
Rendering / Perf Simplifications
components/LazySection.tsx, hooks/use-intersection-observer.tsx
LazySection: removed useMemo and uses direct calculations. use-intersection-observer: stabilized intersection handler with useCallback and adjusted visibility tracking (hasBeenVisible).
Database Caching
lib/db.ts
Introduced a React cache-wrapped internal function (getCollectionCached) and preserved exported getCollection() signature to return the cached result.
API Route (comments only)
app/api/contact/route.ts
Comment text clarified ordering around webhook send vs rate-limit update; no functional changes.
Configuration
package.json
Narrowed lint-staged glob patterns from **/*.{...} to *.{...} (root-only), changing which files are targeted by lint-staged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A tidy hop, a newline placed just right,

Icons find their names and shine in light,
Metadata hums the new refrain,
CSS makes hover simple again,
Cache keeps things cozy through the night. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(ui): optimize component performance' aligns with the main changes: component optimizations including CSS-driven hovers, icon mapping, IntersectionObserver callbacks, debounced scroll handling, and useMemo removal. However, the changeset also includes significant non-UI changes: branding/copy updates across multiple files, metadata changes, API improvements (Discord webhook sequencing), and database caching with React.cache. The title covers only the UI optimization portion and omits these other substantive changes.
Docstring Coverage ✅ Passed Docstring coverage is 93.10% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 36 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/api/contact/route.ts">

<violation number="1" location="app/api/contact/route.ts:101">
P1: Rate-limit timestamp is persisted even when the Discord webhook fails, which can block legitimate retries after unsuccessful submissions.</violation>
</file>

<file name="components/Navbar.tsx">

<violation number="1" location="components/Navbar.tsx:162">
P2: The scroll listener callback is recreated every render, causing unnecessary listener re-subscription churn.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@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

🧹 Nitpick comments (1)
components/Navbar.tsx (1)

15-32: Keep the debounced scroll handler stable and dispose pending timers.

Because Line 162 passes a fresh inline callback into useDebouncedCallback(), handleScroll changes every render and Line 166 tears down/re-adds the window listener each time. The hook also leaves the last timeout alive after unmount. Storing the latest callback in a ref makes this optimization actually reduce work.

♻️ Proposed fix
 function useDebouncedCallback<T extends (...args: unknown[]) => unknown>(
   callback: T,
   delay: number,
 ): (...args: Parameters<T>) => void {
-  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const callbackRef = useRef(callback);
+
+  useEffect(() => {
+    callbackRef.current = callback;
+  }, [callback]);
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+    };
+  }, []);
 
   return useCallback(
     (...args: Parameters<T>) => {
       if (timeoutRef.current) {
         clearTimeout(timeoutRef.current);
       }
       timeoutRef.current = setTimeout(() => {
-        callback(...args);
+        callbackRef.current(...args);
+        timeoutRef.current = null;
       }, delay);
     },
-    [callback, delay],
+    [delay],
   );
 }

Also applies to: 162-170

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

In `@components/Navbar.tsx` around lines 15 - 32, The debounced handler should be
stable and cancel pending timers on unmount: inside useDebouncedCallback keep
the latest callback in a ref (e.g., latestCallbackRef.current = callback inside
a useEffect) and call that ref from the setTimeout instead of closing over the
changing callback so the returned function (from useCallback) can be memoized
without depending on callback; also add a cleanup effect that clears
timeoutRef.current on unmount to dispose any pending timer; adjust the
useCallback dependency list to only include delay so handleScroll (the caller)
stays stable across renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/contact/route.ts`:
- Around line 92-106: The code currently runs fetch(webhookUrl, ...) and
collection.updateOne(...) in parallel which can mark the sender as rate-limited
even if delivery to Discord fails or return a 500 if the DB update fails after a
successful send; instead, call fetch(webhookUrl, { ...discordMessage }) first
and await it, verify response.ok (or throw/return an error if not), and only
after a successful delivery perform collection.updateOne({ ip }, { $set: {
timestamp: now } }, { upsert: true }); if the DB update fails after a successful
fetch, log the error (do not return a failure to the client) or retry
asynchronously so the client sees success but the rate slot is only consumed on
confirmed delivery; refer to webhookUrl, discordMessage, fetch(...) and
collection.updateOne(...) to find and change the logic.

In `@lib/db.ts`:
- Around line 23-28: The comment and use of React.cache around
getCollectionCached is misleading for Route Handlers; remove the React.cache
wrapper from getCollectionCached (or replace it with a plain async function) and
update the comment to state that memoization via React.cache only applies to
Server Components, not Route Handlers, since __dbConnectionPromise already
coalesces global concurrent connections; locate getCollectionCached and
getCollection in this file and remove the cache(...) wrapper and/or adjust the
comment to reference React.cache and __dbConnectionPromise accordingly.

---

Nitpick comments:
In `@components/Navbar.tsx`:
- Around line 15-32: The debounced handler should be stable and cancel pending
timers on unmount: inside useDebouncedCallback keep the latest callback in a ref
(e.g., latestCallbackRef.current = callback inside a useEffect) and call that
ref from the setTimeout instead of closing over the changing callback so the
returned function (from useCallback) can be memoized without depending on
callback; also add a cleanup effect that clears timeoutRef.current on unmount to
dispose any pending timer; adjust the useCallback dependency list to only
include delay so handleScroll (the caller) stays stable across renders.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: adee6458-20e8-4d13-8e42-87cb8013a94d

📥 Commits

Reviewing files that changed from the base of the PR and between 5c4f7e8 and 2a90cc5.

📒 Files selected for processing (36)
  • app/about/layout.tsx
  • app/about/page.tsx
  • app/api/contact/route.ts
  • app/apple-icon.tsx
  • app/contact/layout.tsx
  • app/contact/page.tsx
  • app/icon.tsx
  • app/layout.tsx
  • app/open-source/layout.tsx
  • app/open-source/page.tsx
  • app/opengraph-image.tsx
  • app/page.tsx
  • app/privacy/page.tsx
  • app/robots.ts
  • app/services/hosting/page.tsx
  • app/services/layout.tsx
  • app/services/page.tsx
  • app/services/seo/page.tsx
  • app/services/seo/seo-content.tsx
  • app/services/web-design/page.tsx
  • app/services/web-development/page.tsx
  • app/services/web-development/web-development-content.tsx
  • app/sitemap.ts
  • app/terms/page.tsx
  • app/twitter-image.tsx
  • components/About.tsx
  • components/Contact.tsx
  • components/Features.tsx
  • components/Footer.tsx
  • components/Hero.tsx
  • components/LazySection.tsx
  • components/Navbar.tsx
  • components/PageTransition.tsx
  • hooks/use-intersection-observer.tsx
  • lib/db.ts
  • package.json

Copy link
Contributor

@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

🧹 Nitpick comments (1)
components/Navbar.tsx (1)

72-86: Respect reduced-motion for the new hover effects.

The glitch animation now runs on every hover, including for users who request reduced motion. Please gate the animation and transition classes with motion-safe/motion-reduce.

💡 Suggested fix
-            className="relative inline-block group-hover:animate-[glitch_0.4s_ease_forwards]"
+            className="relative inline-block motion-safe:group-hover:animate-[glitch_0.4s_ease_forwards]"
...
-        className="pointer-events-none absolute inset-0 -z-10 opacity-0 blur-sm transition-opacity duration-300 group-hover:opacity-100"
+        className="pointer-events-none absolute inset-0 -z-10 opacity-0 blur-sm motion-safe:transition-opacity motion-safe:duration-300 group-hover:opacity-100"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Navbar.tsx` around lines 72 - 86, The hover/glitch animation and
transitions in Navbar (the span with className "relative inline-block
group-hover:animate-[glitch_0.4s_ease_forwards]" and the spans using
"transition-[width] duration-300 ease-out" and "transition-opacity duration-300
group-hover:opacity-100") need to respect reduced-motion: wrap
hover/animation/transition classes with motion-safe (e.g. change
group-hover:animate-... to motion-safe:group-hover:animate-...) and add
motion-reduce fallbacks to disable motion (e.g. motion-reduce:animate-none and
motion-reduce:transition-none or motion-reduce:opacity-100 as appropriate) so
users who request reduced motion get no animations/transitions. Ensure you
update all related spans (the per-char span, the underline span, and the glow
span) to include these prefixes.
🤖 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/Navbar.tsx`:
- Around line 15-32: The debounced hook useDebouncedCallback currently leaves a
pending timer after unmount; add a cleanup effect inside useDebouncedCallback
that on unmount clears timeoutRef.current (clearTimeout) and sets it to null so
no delayed callback runs after the component unmounts; reference timeoutRef in
the cleanup (useEffect(() => () => { if (timeoutRef.current) {
clearTimeout(timeoutRef.current); timeoutRef.current = null } }, [])) so the
timer is cancelled when useDebouncedCallback's consumer unmounts.
- Around line 170-174: The navbar's scroll state (isScrolled) must be set
synchronously because handleScroll is debounced; compute and set the initial
isScrolled value immediately (e.g., from window.scrollY or
document.documentElement.scrollTop compared to your threshold) before
registering the debounced listener in useEffect, then attach the existing
debounced handleScroll for subsequent events and remove it on cleanup; update
references to useEffect, handleScroll, isScrolled, and setIsScrolled to reflect
this two-step init + subscribe approach.

---

Nitpick comments:
In `@components/Navbar.tsx`:
- Around line 72-86: The hover/glitch animation and transitions in Navbar (the
span with className "relative inline-block
group-hover:animate-[glitch_0.4s_ease_forwards]" and the spans using
"transition-[width] duration-300 ease-out" and "transition-opacity duration-300
group-hover:opacity-100") need to respect reduced-motion: wrap
hover/animation/transition classes with motion-safe (e.g. change
group-hover:animate-... to motion-safe:group-hover:animate-...) and add
motion-reduce fallbacks to disable motion (e.g. motion-reduce:animate-none and
motion-reduce:transition-none or motion-reduce:opacity-100 as appropriate) so
users who request reduced motion get no animations/transitions. Ensure you
update all related spans (the per-char span, the underline span, and the glow
span) to include these prefixes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1de11a48-eb4d-4a15-98be-c50852a5418b

📥 Commits

Reviewing files that changed from the base of the PR and between 2a90cc5 and b86973c.

📒 Files selected for processing (2)
  • app/api/contact/route.ts
  • components/Navbar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/api/contact/route.ts

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/Navbar.tsx">

<violation number="1" location="components/Navbar.tsx:91">
P2: `motion-reduce:w-full` makes inactive links always look active for reduced-motion users. Remove the forced full width and only disable the transition.</violation>

<violation number="2" location="components/Navbar.tsx:96">
P3: `motion-reduce:opacity-100` makes the hover glow permanently visible in reduced-motion mode. Keep hover behavior and only remove transition/animation.</violation>

<violation number="3" location="components/Navbar.tsx:160">
P2: Reading `window.scrollY` in the state initializer can desync initial client/server render state for `data-nav-state`. Initialize to a stable value and sync after mount.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

className={`pointer-events-none absolute -bottom-1 left-0 h-0.5 bg-coral transition-[width] duration-300 ease-out ${
isActive ? "w-full" : "w-0 group-hover:w-full"
className={`pointer-events-none absolute -bottom-1 left-0 h-0.5 bg-coral transition-[width] duration-300 ease-out motion-reduce:transition-none ${
isActive ? "w-full" : "w-0 group-hover:w-full motion-reduce:w-full"
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 7, 2026

Choose a reason for hiding this comment

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

P2: motion-reduce:w-full makes inactive links always look active for reduced-motion users. Remove the forced full width and only disable the transition.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Navbar.tsx, line 91:

<comment>`motion-reduce:w-full` makes inactive links always look active for reduced-motion users. Remove the forced full width and only disable the transition.</comment>

<file context>
@@ -69,21 +79,21 @@ const NavLink = memo(function NavLink({
-        className={`pointer-events-none absolute -bottom-1 left-0 h-0.5 bg-coral transition-[width] duration-300 ease-out ${
-          isActive ? "w-full" : "w-0 group-hover:w-full"
+        className={`pointer-events-none absolute -bottom-1 left-0 h-0.5 bg-coral transition-[width] duration-300 ease-out motion-reduce:transition-none ${
+          isActive ? "w-full" : "w-0 group-hover:w-full motion-reduce:w-full"
         }`}
       />
</file context>
Suggested change
isActive ? "w-full" : "w-0 group-hover:w-full motion-reduce:w-full"
isActive ? "w-full" : "w-0 group-hover:w-full"
Fix with Cubic

const pathname = usePathname();
const [isScrolled, setIsScrolled] = useState(false);
// Initialize scroll state from window to avoid setState in effect
const [isScrolled, setIsScrolled] = useState(() => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 7, 2026

Choose a reason for hiding this comment

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

P2: Reading window.scrollY in the state initializer can desync initial client/server render state for data-nav-state. Initialize to a stable value and sync after mount.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Navbar.tsx, line 160:

<comment>Reading `window.scrollY` in the state initializer can desync initial client/server render state for `data-nav-state`. Initialize to a stable value and sync after mount.</comment>

<file context>
@@ -146,7 +156,11 @@ GlitchLogo.displayName = "GlitchLogo";
   const pathname = usePathname();
-  const [isScrolled, setIsScrolled] = useState(false);
+  // Initialize scroll state from window to avoid setState in effect
+  const [isScrolled, setIsScrolled] = useState(() => {
+    if (typeof window === "undefined") return false;
+    return window.scrollY > 10;
</file context>
Fix with Cubic

className={`pointer-events-none absolute inset-0 -z-10 blur-sm transition-opacity duration-300 ${
isHovered ? "opacity-100" : "opacity-0"
}`}
className="pointer-events-none absolute inset-0 -z-10 opacity-0 blur-sm transition-opacity duration-300 group-hover:opacity-100 motion-reduce:opacity-100 motion-reduce:transition-none"
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 7, 2026

Choose a reason for hiding this comment

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

P3: motion-reduce:opacity-100 makes the hover glow permanently visible in reduced-motion mode. Keep hover behavior and only remove transition/animation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Navbar.tsx, line 96:

<comment>`motion-reduce:opacity-100` makes the hover glow permanently visible in reduced-motion mode. Keep hover behavior and only remove transition/animation.</comment>

<file context>
@@ -69,21 +79,21 @@ const NavLink = memo(function NavLink({
       {/* Glow - CSS-only hover */}
       <span
-        className="pointer-events-none absolute inset-0 -z-10 opacity-0 blur-sm transition-opacity duration-300 group-hover:opacity-100"
+        className="pointer-events-none absolute inset-0 -z-10 opacity-0 blur-sm transition-opacity duration-300 group-hover:opacity-100 motion-reduce:opacity-100 motion-reduce:transition-none"
         style={{
           background:
</file context>
Suggested change
className="pointer-events-none absolute inset-0 -z-10 opacity-0 blur-sm transition-opacity duration-300 group-hover:opacity-100 motion-reduce:opacity-100 motion-reduce:transition-none"
className="pointer-events-none absolute inset-0 -z-10 opacity-0 blur-sm transition-opacity duration-300 group-hover:opacity-100 motion-reduce:transition-none"
Fix with Cubic

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