Skip to content

Add multi-language support with i18n and locale routing#5

Merged
Mostafatalaat770 merged 10 commits into
mainfrom
claude/improve-seo-listings-ba4Aa
Feb 26, 2026
Merged

Add multi-language support with i18n and locale routing#5
Mostafatalaat770 merged 10 commits into
mainfrom
claude/improve-seo-listings-ba4Aa

Conversation

@bonuz-bot

Copy link
Copy Markdown
Contributor

Summary

Implemented comprehensive internationalization (i18n) support for the Bonuz Technology website, enabling content delivery in English, Arabic, German, and Simplified Chinese with proper locale-based routing, RTL support, and dynamic metadata generation.

Key Changes

Internationalization Infrastructure

  • Created lib/i18n.ts with locale configuration, type definitions, and dictionary loader
  • Added language dictionary files for English, Arabic, German, and Chinese (lib/dictionaries/)
  • Implemented LanguageSwitcher component with dropdown UI for language selection
  • Added middleware for automatic locale detection and redirection to default locale

Routing & Layout

  • Migrated from single root layout to locale-based routing with app/[locale]/layout.tsx
  • Moved metadata generation to dynamic generateMetadata() function that loads locale-specific content
  • Created app/[locale]/page.tsx for locale-specific home page rendering
  • Updated root layout to act as a simple wrapper returning children

Component Updates

  • Refactored HomePage component to accept dict and locale props for dynamic content rendering
  • Updated all hardcoded text to use dictionary values
  • Fixed logo and navigation links to include locale prefix

Styling & Localization

  • Added RTL (right-to-left) support for Arabic with CSS dir attribute handling
  • Implemented locale-specific font loading (Noto Sans Arabic for Arabic, Noto Sans SC for Chinese)
  • Added CSS classes for RTL layout adjustments (.lang-switcher, .logo, .features-list)
  • Updated transitions to be more specific and added motion preference detection

SEO & Metadata

  • Enhanced metadata with locale-specific titles and descriptions
  • Added language alternates to metadata for multi-language SEO
  • Expanded JSON-LD structured data with additional schema types (FAQPage, BreadcrumbList)
  • Updated sitemap to include all locale variants with proper alternates

Performance & UX

  • Optimized SpotlightCard component with useCallback and intersection observer pattern
  • Added viewport visibility detection to Galaxy component to prevent unnecessary rendering
  • Improved language switcher with smooth animations and accessibility attributes

Configuration Updates

  • Enhanced robots.txt with crawler-specific rules for Google, Bing, and Yandex
  • Updated 404 page to redirect to /en instead of /

Notable Implementation Details

  • Locale detection uses Next.js 15 async params pattern
  • Dictionary loading is lazy-loaded per locale to optimize bundle size
  • RTL support uses CSS logical properties (inset-inline-start, padding-inline-start) for better maintainability
  • Language switcher uses client-side navigation with useRouter for seamless locale switching
  • Metadata generation includes proper OpenGraph locale mapping (e.g., zh_CN for Chinese)

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE

- Add verification meta tags for Google Search Console, Bing Webmaster
  Tools, and Yandex Webmaster (placeholder values to be replaced)
- Expand keyword list from 14 to 29 terms for broader search coverage
- Update page title and OG/Twitter titles to include target keywords
- Add geo meta tags (geo.region, geo.placename, ICBM) for local SEO
- Add hreflang alternate language link for en-US
- Enhance robots.txt with specific directives for Googlebot, Bingbot,
  Yandex and their sub-crawlers, plus Host directive for Yandex
- Disallow /_next/static/ and /api/ paths from crawlers
- Expand sitemap from 1 to 5 entries covering all page sections
- Add BreadcrumbList structured data for site navigation
- Add FAQPage schema with 4 Q&A entries for rich search results

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE
- Add IntersectionObserver to Galaxy component to pause the
  requestAnimationFrame loop when the canvas is not visible in the
  viewport, eliminating unnecessary GPU work from two simultaneous
  WebGL renderers
- Cache getBoundingClientRect() in Galaxy and update on resize/scroll
  (debounced) instead of calling it on every mouse move event
- Rewrite SpotlightCard to manipulate DOM directly via refs instead of
  triggering React re-renders on every mouse move via setState
- Gate scroll-behavior: smooth behind prefers-reduced-motion media query
- Replace transition: all with specific properties (transform, box-shadow)
  on .btn, .work-card, and .btn-form to avoid unnecessary style recalcs

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE
Google is verified at the domain level via DNS in Search Console,
so no HTML meta tag is needed. Bing can be imported from GSC later.
Keeps only the Yandex verification meta tag.

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE
…tion

- Change "bonuz Social Wallet" to "bonuz Lifestyle Wallet" across page and structured data
- Replace "Request Intro" section with "Project Intake Request" with updated copy
- Update nav button text from "Request intro" to "Project intake"

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE
- Create i18n infrastructure with typed dictionaries for EN, AR, DE, ZH
- Add locale-based routing via [locale] dynamic segment and middleware
- Extract page content into HomePage client component with dictionary props
- Add stylish glassmorphism language switcher (fixed top-right corner)
- Full RTL support for Arabic (mirrored layout, directional CSS)
- Load locale-specific Google Fonts (Noto Sans Arabic, Noto Sans SC)
- Dynamic metadata per locale (title, description, OpenGraph, hreflang)
- English remains the default language (/ redirects to /en)

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE
- Generate sitemap entries for all 4 locales (en, ar, de, zh) with
  hreflang alternates for proper cross-language SEO indexing
- English locale gets priority 1, others get 0.9
- Section anchors now use /en prefix for consistency
- Fix not-found.tsx links to point to /en instead of bare /

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE
@vercel

vercel Bot commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
bounz-tech Ready Ready Preview, Comment Feb 26, 2026 11:06am

Request Review

@claude

claude Bot commented Feb 26, 2026

Copy link
Copy Markdown

PR Review: Multi-language i18n & Locale Routing

This is a well-structured implementation of internationalization. The architecture choices are sound and the code quality is generally high. Here are my findings:


Bugs / Issues

1. Locale path-switching bug in LanguageSwitcher (components/LanguageSwitcher.tsx:27)

const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);

String.prototype.replace only replaces the first occurrence, but more importantly, if the pathname contains the locale string in another position (e.g., /en/something-en-related), the replacement will be incorrect. Use a more precise replacement anchored to the start:

const newPath = pathname.replace(new RegExp(`^/${locale}`), `/${newLocale}`);

2. locales is duplicated across middleware.ts and lib/i18n.ts

middleware.ts declares its own const locales = ["en", "ar", "de", "zh"] instead of importing from lib/i18n.ts. If a new locale is added to lib/i18n.ts, the middleware will silently fail to recognize it, causing redirect loops. Fix:

// middleware.ts
import { locales, defaultLocale } from "@/lib/i18n";

Note: Check if this import causes issues with Edge Runtime — if so, extract just the constants into a separate file (e.g., lib/i18n-config.ts) without any async code.

3. No error handling in getDictionary (lib/i18n.ts:65)

If a dictionary import fails (e.g., a deploy with a missing file), the error propagates uncaught. Consider wrapping with a fallback to English:

export async function getDictionary(locale: Locale): Promise<Dictionary> {
  return dictionaries[locale]().catch(() => dictionaries["en"]());
}

Performance

4. Google Fonts loaded via <link> tags instead of next/font (app/[locale]/layout.tsx:386)

The original implementation used next/font/google which self-hosts fonts, eliminating the external CDN request, preventing layout shift (FOUT), and automatically inlining the critical CSS. The current approach makes an additional network request to fonts.googleapis.com on each page load.

For Arabic and Chinese, next/font supports these via the subsets option. For multi-locale dynamic font loading, consider using next/font with conditional rendering or a font-display: swap strategy with preloading only the current locale's font.


SEO

5. Sitemap includes URL anchors (app/sitemap.ts:30-45)

Fragment identifiers (#what-we-do, #our-work, etc.) are not valid in sitemaps — Google's documentation explicitly states that crawlers ignore the fragment portion. These entries add noise without benefit and should be removed.

6. Missing x-default hreflang (app/[locale]/layout.tsx:23-27)

The alternates.languages object should include an x-default entry per Google's i18n SEO guidelines:

alternatesLanguages["x-default"] = `${siteUrl}/en`;

7. not-found.tsx hardcodes /en

Users hitting a 404 from a non-English locale will be sent to the English home page. This is a minor UX issue — ideally the 404 page should link to the user's current locale, though this is difficult since 404 pages don't have access to the locale context.


Accessibility

8. LanguageSwitcher is missing keyboard navigation (components/LanguageSwitcher.tsx)

The dropdown button has aria-expanded but is missing:

  • aria-haspopup="listbox" on the trigger button
  • role="listbox" on the dropdown container
  • role="option" and aria-selected on each language option
  • Escape key handler to close the dropdown
  • Arrow key navigation between options

Minimal fix:

<button
  aria-haspopup="listbox"
  aria-expanded={open}
  ...
>
// In useEffect, add:
function handleKeyDown(e: KeyboardEvent) {
  if (e.key === "Escape") setOpen(false);
}
document.addEventListener("keydown", handleKeyDown);

Minor / Suggestions

9. No browser language detection in middleware

The middleware always redirects to en regardless of the visitor's Accept-Language header. For a better first-visit UX, consider detecting the preferred locale:

const acceptLanguage = request.headers.get("accept-language") ?? "";
const preferredLocale = locales.find((l) => acceptLanguage.startsWith(l)) ?? defaultLocale;

This is opt-in complexity — acceptable to defer, just worth noting.

10. Array index used as key in features list (components/HomePage.tsx:85)

{dict.whatWeDo.features.map((feature, i) => (
  <li key={i}>{feature}</li>
))}

Since these are static translated strings, using the feature text itself as the key would be more stable: key={feature}. Low priority.

11. RTL arrow direction handled in CSS content, not semantically

html[dir="rtl"] .features-list li::before {
  content: "←";
}

This is fine, but a Unicode-neutral approach like "▸" or just a CSS rotation would be more maintainable across both text directions.


What's Done Well

  • The Dictionary type is well-structured and ensures type safety across all 4 locales at compile time.
  • Lazy-loading dictionaries per locale (import("./dictionaries/en")) keeps the initial bundle small.
  • The SpotlightCard refactor from useState to direct DOM manipulation is a meaningful perf improvement — avoids re-renders on every mouse move.
  • The IntersectionObserver addition to Galaxy.tsx prevents WebGL rendering when the component is off-screen — good resource management.
  • RTL support using CSS logical properties (inset-inline-start, padding-inline-start) is the correct modern approach.
  • The generateStaticParams export in the locale layout enables static generation of all locale pages at build time.
  • The scroll-behavior: smooth is correctly wrapped in @media (prefers-reduced-motion: no-preference).

Summary: The two most important issues to address before merging are the locale duplication in middleware (#2) and the path replacement bug in LanguageSwitcher (#1). The next/font regression (#4) would be worth addressing for production performance.

@claude

claude Bot commented Feb 26, 2026

Copy link
Copy Markdown

PR Review: Add multi-language support with i18n and locale routing

Overall this is a solid implementation of i18n for a Next.js site. The architecture choices are sound and the code is generally clean. Here are my findings:


Bugs / Correctness Issues

1. locales duplicated between lib/i18n.ts and middleware.ts

middleware.ts defines its own locales = ["en", "ar", "de", "zh"] array instead of importing from lib/i18n.ts. If a new locale is added to lib/i18n.ts, middleware.ts must be updated separately or locale routing will break silently.

Suggestion: import from the shared module:

import { locales, defaultLocale } from "@/lib/i18n";

2. JSON-LD WebPage URL points to root, not the locale URL

In app/[locale]/layout.tsx, the static structuredData object references siteUrl for the WebPage entry, but the canonical URL is now ${siteUrl}/${locale}. Since structuredData is a module-level constant it cannot use the locale. Consider making it a function that accepts locale, or moving the WebPage/WebSite entries into generateMetadata where locale is available.

3. generateMetadata returns {} on invalid locale instead of calling notFound()

if (!locales.includes(locale as Locale)) return {};

This means an invalid locale path gets served with empty metadata before the layout's notFound() fires. Both should fail consistently — call notFound() in generateMetadata too.

4. LanguageSwitcher path replacement is not anchored to the path prefix

const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);

String.replace() replaces the first occurrence anywhere in the string. For robustness, anchor it to the path start:

const newPath = pathname.replace(new RegExp(`^/${locale}`), `/${newLocale}`);

Performance Concerns

5. Dropping next/font for Google Fonts <link> tags is a regression

The previous implementation used next/font/google for Space Grotesk, which provides font subsetting, self-hosting, preloading, and FOUT prevention. This PR replaces it with plain <link rel="stylesheet"> tags — an external network request on every page load with no preloading. globals.css now also uses a hard-coded font-family: "Space Grotesk" string instead of the CSS variable.

For the locale-specific fonts, next/font/google can be used with conditional loading based on locale. At minimum, consider adding <link rel="preload"> for the font stylesheet.

6. Sitemap includes URL fragment anchors — ignored by search engines

{ url: `${baseUrl}/en#what-we-do`, ... }
{ url: `${baseUrl}/en#our-work`, ... }

Search engines universally ignore fragment identifiers in sitemaps. These entries add noise without SEO benefit. Remove them and keep only the locale root URLs.


Accessibility Issues

7. LanguageSwitcher dropdown missing ARIA roles and keyboard navigation

The button has aria-expanded (good), but is missing:

  • aria-haspopup="listbox" on the trigger button
  • role="listbox" or role="menu" on the dropdown container
  • role="option" or role="menuitem" on each language option
  • Keyboard support: Escape to close, arrow keys to navigate options

Minor / Code Quality

8. Hard-coded "/en" in not-found.tsx

Import defaultLocale from lib/i18n.ts instead of hard-coding the string, for easier future maintenance.

9. features-list uses array index as React key

{dict.whatWeDo.features.map((feature, i) => (
  <li key={i}>{feature}</li>
))}

Acceptable since this list is static, but using the feature text itself as the key would be more stable.


Positive Highlights

  • Galaxy IntersectionObserver — pausing the WebGL animation loop when off-screen is an excellent performance win. Cleanup (disconnect, cancelAnimationFrame) is correctly handled in the effect return.
  • SpotlightCard DOM ref optimization — replacing React state with direct overlayRef.current.style mutations for the spotlight gradient eliminates re-renders on every mousemove. Well done.
  • CSS logical properties (padding-inline-start, inset-inline-start) for RTL — the right approach for maintainable bidirectional layouts.
  • Lazy dictionary loading via dynamic imports — correct code-splitting pattern per locale.
  • generateStaticParams — good that SSG is preserved for all locales.
  • Specific CSS transitions instead of transition: all — better for compositor performance.

Summary

Category Count
Bugs / Correctness 4
Performance 2
Accessibility 1
Minor 2

The most impactful items before merging: duplicated locales constant in middleware (#1), the next/font regression (#5), and ARIA roles on the language switcher (#7).

… public directory

Signed-off-by: Mostafa Talaat <mostafatalaat770@gmail.com>
@claude

claude Bot commented Feb 26, 2026

Copy link
Copy Markdown

PR Review: Multi-language i18n Support

Overall this is a solid implementation of i18n with locale-based routing. The architecture follows Next.js 15 conventions well and the RTL support is thorough. Here are my findings:


🐛 Bugs

1. Fragile locale switching in LanguageSwitcher.tsx

const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);

String.replace() replaces only the first occurrence, and the locale string can appear elsewhere in the path. If a URL were /en/something-en-related, it would incorrectly produce /de/something-de-related. Use a regex anchored at the start instead:

const newPath = pathname.replace(new RegExp(`^/${locale}`), `/${newLocale}`);

2. Redundant locale validation in app/[locale]/page.tsx

The layout (app/[locale]/layout.tsx) already calls notFound() for invalid locales before children render. The duplicate check in page.tsx is dead code and adds unnecessary overhead — remove it.

3. not-found.tsx hardcodes /en

Users browsing in German or Arabic who hit a 404 will be redirected to /en. Since not-found.tsx doesn't have access to locale params, consider using the middleware redirect (to /en as fallback) or storing locale in a cookie/header for the 404 handler.


⚡ Performance Regression

4. Font loading reverts away from next/font

The original code used next/font/google (Space_Grotesk), which provides:

  • Automatic font subsetting and optimization
  • Inlined font-face declarations (no extra network round-trip)
  • Preload hints managed by Next.js

The new code replaces this with a manual <link rel="stylesheet" href="https://fonts.googleapis.com/..."> in the layout <head>. This requires an extra DNS lookup + network round-trip to fonts.googleapis.com on each page load.

Consider using next/font/google for Space Grotesk (keeping the CSS variable approach) and only falling back to manual loading for Noto Sans Arabic and Noto Sans SC, which aren't easily supported via next/font for RTL/CJK use cases. Alternatively, load all three via the manual link but add rel="preload" for the base font.

Also, body { font-family: "Space Grotesk", sans-serif; } is now relying on the browser finding the font by name rather than via a CSS variable — if the stylesheet fails to load, there's no fallback chain.


🔧 Code Quality

5. Locale array duplicated in middleware.ts

middleware.ts re-declares const locales = ["en", "ar", "de", "zh"] instead of importing from lib/i18n.ts. If a new locale is added to i18n.ts, the middleware won't redirect to it until manually updated:

// middleware.ts
import { locales, defaultLocale } from "@/lib/i18n";

6. key={i} for static feature list in HomePage.tsx

{dict.whatWeDo.features.map((feature, i) => (
  <li key={i}>{feature}</li>
))}

Using array index as key is fine here since the list is static, but it's worth noting this pattern suppresses the React warning while providing no reconciliation benefit. A stable key derived from content (e.g., key={feature.slice(0, 20)}) would be more idiomatic for a static list.

7. Structured data WebPage URL doesn't include locale

In app/[locale]/layout.tsx:

{
  "@type": "WebPage",
  "@id": `${siteUrl}/#webpage`,
  url: siteUrl,  // Should be `${siteUrl}/${locale}`

The url field points to https://bonuz.tech rather than the locale-specific URL, which is incorrect for a per-locale layout. The @id is shared across all locales which may also confuse structured data parsers.


🔍 SEO

8. Missing x-default hreflang

Google recommends including x-default in the alternates to indicate the fallback page. In generateMetadata:

alternatesLanguages["x-default"] = `${siteUrl}/en`;

9. Fragment URLs in sitemap

app/sitemap.ts includes URLs like ${baseUrl}/en#what-we-do. Search engines ignore fragment identifiers in sitemaps — these entries provide no SEO value. Remove the anchor-based entries and keep only the top-level locale URLs.

10. canonical points to locale URL, not root

Since the root URL (https://bonuz.tech) redirects to /en, consider whether the canonical should be https://bonuz.tech or https://bonuz.tech/en. Currently it's set to ${siteUrl}/${locale} which is correct, but the root URL has no canonical in sitemap.


♿ Accessibility

11. Language switcher buttons lack descriptive labels

The <button> elements inside the dropdown have no aria-label:

<button className="lang-option" onClick={() => switchLocale(l)}>

Should be:

<button
  className="lang-option"
  onClick={() => switchLocale(l)}
  aria-label={`Switch language to ${localeNames[l]}`}
>

The trigger button has aria-label="Switch language" but no aria-haspopup="listbox" or aria-controls referencing the dropdown, which limits screen reader usability.


✅ What's Done Well

  • Galaxy.tsx visibility optimization using IntersectionObserver is a great improvement — prevents GPU work when the element is off-screen, with proper cleanup.
  • SpotlightCard.tsx refactor from state-based to ref-based DOM manipulation eliminates unnecessary re-renders on every mouse move. Solid perf win.
  • CSS logical properties (inset-inline-start, padding-inline-start) for RTL are the right approach — better than duplicating rules with [dir="rtl"] overrides.
  • prefers-reduced-motion wrapping the smooth scroll behaviour is a good accessibility improvement.
  • Transition specificity — replacing transition: all 0.3s ease with explicit property lists avoids unintentional transitions on layout-affecting properties.
  • Lazy dictionary loading per locale keeps the initial JS bundle small.
  • Middleware matcher regex is well-constructed to exclude static assets.

Summary

Category Count
Bugs 3
Performance 1 regression
Code quality 3
SEO 3
Accessibility 1

The locale switching path bug (#1) and font loading regression (#4) are the most important to address before merging. The middleware import duplication (#5) is a quick win that prevents future maintenance drift.

- Add fonts.googleapis.com to style-src and fonts.gstatic.com to
  font-src in Content-Security-Policy so Arabic/Chinese fonts load
- Import locale list from lib/i18n.ts in middleware and sitemap
  instead of maintaining separate copies

https://claude.ai/code/session_01USUGkGnYqSa4L9qK9xinkE
@claude

claude Bot commented Feb 26, 2026

Copy link
Copy Markdown

PR Review: Add multi-language support with i18n and locale routing

Overall, this is a solid implementation of internationalization. The architecture is clean, the RTL support is well-handled, and several nice performance improvements are bundled in. Below are observations across categories.


Bugs / Correctness

1. Fragile locale path replacement in LanguageSwitcher (components/LanguageSwitcher.tsx:27)

String.replace() replaces only the first occurrence. If the locale code ever appears elsewhere in a URL segment (e.g., /en/posts/de-overview), this would produce an incorrect path. Use split/join on the first segment instead:

const segments = pathname.split('/');
segments[1] = newLocale;
const newPath = segments.join('/');

2. Hardcoded /en in app/not-found.tsx (lines 17 and 25)

If the default locale ever changes, these links break silently. Import and use the defaultLocale constant from @/lib/i18n instead of hardcoding /en.

3. cachedRect initialized before canvas is attached (components/Galaxy.tsx)

cachedRect is set via getBoundingClientRect() immediately, but ctn.appendChild(gl.canvas) comes a few lines later. The rect is on the container (not the canvas) so it is low-risk, but layout shifts between setup and first render could leave the cache stale until the next scroll fires the debounced update.


Performance

4. Google Fonts via link tags instead of next/font/google (app/[locale]/layout.tsx)

The previous code used next/font/google (self-hosted, zero layout shift, automatic subsetting). The new approach loads fonts externally at runtime, which requires an extra network round-trip to Google's CDN, can cause a Flash of Unstyled Text (FOUT), and loses Next.js's automatic font size adjustment that prevents CLS. Supporting multiple font families per locale is achievable with next/font/google too. Consider whether this tradeoff is intentional, as it may impact Core Web Vitals.

5. useCallback in SpotlightCard without React.memo (components/SpotlightCard.tsx)

SpotlightCard is not wrapped in React.memo, so parent re-renders still recreate all callbacks regardless of useCallback. The direct DOM manipulation via overlayRef is an excellent improvement that avoids React state churn on every mousemove, but the useCallback wrappers add overhead without measurable benefit here. Harmless, just unnecessary.


Code Quality

6. Array index used as React key (components/HomePage.tsx)

dict.whatWeDo.features.map((feature, i) => <li key={i}>{feature}</li>)

For this static list the risk is low, but using the feature text as the key (key={feature}) is more semantically correct.

7. JSON-LD WebPage schema uses the base URL, not locale URL (app/[locale]/layout.tsx)

The structuredData is a module-level constant shared across all locales, so WebPage and WebSite schema always points to https://bonuz.tech rather than https://bonuz.tech/en. The comment "kept in English for schema.org" is valid, but the url fields should ideally reflect the canonical locale URL for accurate Google attribution.

8. Dynamic lastModified in sitemap (app/sitemap.ts)

Using new Date() means every sitemap request returns the current timestamp for every URL, signaling to crawlers that everything changed on every visit. Consider using fixed dates tied to actual content changes.


Security

9. dangerouslySetInnerHTML for JSON-LD is safe here

The structured data is a hardcoded module-level constant with no user input, so there is no XSS risk. The CSP updates to allow fonts.googleapis.com in style-src and fonts.gstatic.com in font-src are also correct.


What's Well Done

  • RTL support using CSS logical properties (inset-inline-start, padding-inline-start) is the right approach for long-term maintainability.
  • IntersectionObserver in Galaxy — stopping the WebGL render loop when off-screen is a meaningful, correct optimization.
  • SpotlightCard direct DOM manipulation avoids React reconciler overhead on every mousemove, a significant improvement.
  • prefers-reduced-motion wrapping of scroll behavior is good accessibility practice.
  • Lazy-loaded dictionaries via dynamic import() keeps per-locale code out of the main bundle.
  • Locale-specific metadata and alternates are correctly structured for hreflang SEO.
  • Middleware correctly avoids redirect loops and skips static assets.
  • Replacing the remote Cloudinary logo URL with a local /logo.svg is better for reliability and performance.
  • Replacing transition: all with specific property transitions is a correct performance improvement.

Summary

Category Severity Item
Bug Medium Fragile switchLocale path replacement
Bug Low Hardcoded /en in not-found.tsx
Bug Low cachedRect stale window
Performance Medium Font loading via link tags vs next/font/google
Performance Low Unnecessary useCallback in SpotlightCard
Code Quality Low Array index as React key
Code Quality Low JSON-LD WebPage URL not locale-specific
Code Quality Low Dynamic lastModified in sitemap

The two items worth addressing before merge: the fragile switchLocale path replacement and the font loading strategy (if Core Web Vitals matter). The /en hardcoding in not-found.tsx is a quick one-line fix.

@Mostafatalaat770 Mostafatalaat770 merged commit 1b0fecb into main Feb 26, 2026
3 checks passed
@Mostafatalaat770 Mostafatalaat770 deleted the claude/improve-seo-listings-ba4Aa branch February 26, 2026 11:29
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.

3 participants