Skip to content

Fix/force dynamic storefront - #170

Closed
pawelekbyra wants to merge 76 commits into
spree:mainfrom
pawelekbyra:fix/force-dynamic-storefront
Closed

Fix/force dynamic storefront#170
pawelekbyra wants to merge 76 commits into
spree:mainfrom
pawelekbyra:fix/force-dynamic-storefront

Conversation

@pawelekbyra

@pawelekbyra pawelekbyra commented Jul 9, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Added a language selector in the header and mobile menu.
    • Updated storefront navigation to use locale-based URLs.
    • Added scheduled price updates and manual cache refresh support.
  • Bug Fixes

    • Improved storefront pages to load more reliably when market data is unavailable.
    • Fixed product, category, checkout, and account links to work consistently with the new URL format.
    • Updated image loading and store branding display for a smoother shopping experience.

pawelekbyra and others added 30 commits July 5, 2026 19:28
Handle unavailable Spree data during static prerendering by returning safe fallbacks for currency and featured products.
- vercel.json: Complete Vercel configuration with env var documentation
- VERCEL_DEPLOYMENT.md: Step-by-step guide for successful deployment
- Defines all required and optional environment variables
- Includes troubleshooting for common build failures

Required env vars for production:
- NEXT_PUBLIC_SITE_URL
- SPREE_API_URL
- SPREE_PUBLISHABLE_KEY

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBHpzVAh3bsKPYXbKNaHCk
Vercel doesn't accept nodeVersion in vercel.json. Node version can be
configured in Vercel UI if needed (defaults to latest LTS).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBHpzVAh3bsKPYXbKNaHCk
Remove invalid 'default' property from env vars. Vercel doesn't support
defaults in vercel.json - they must be set in Vercel UI or .env.local.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBHpzVAh3bsKPYXbKNaHCk
Vercel automatically detects and configures Next.js projects.
Manual vercel.json causes env var validation issues.

Environment variables should be set in Vercel UI only.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBHpzVAh3bsKPYXbKNaHCk
TypeScript was unable to narrow the type of product to ensure
it's defined at the canonical URL generation point. Added explicit
check to satisfy type checker.

Co-Authored-By: Claude <noreply@anthropic.com>
Add explicit StoreProduct type annotation to ensure TypeScript
knows product is always defined after the try-catch block.
This satisfies the type checker for all product property accesses.

Co-Authored-By: Claude <noreply@anthropic.com>
Use Awaited<ReturnType<typeof getCachedProduct>> to infer the
correct product type instead of relying on a non-existent type.

Co-Authored-By: Claude <noreply@anthropic.com>
TypeScript control flow doesn't recognize that notFound() throws,
so we need explicit non-null assertions to tell the compiler that
product is always defined at each usage point.

Co-Authored-By: Claude <noreply@anthropic.com>
Add all required PaginationMeta fields to emptyCategoryProductsResponse
with sensible defaults for empty responses.

Co-Authored-By: Claude <noreply@anthropic.com>
Add in, previous, next fields and use 'as unknown as' to avoid
further type compatibility issues with SDK's PaginationMeta.

Co-Authored-By: Claude <noreply@anthropic.com>
Apply consistent empty pagination response format with all
required PaginationMeta fields across products and categories.

Co-Authored-By: Claude <noreply@anthropic.com>
Add filters, sort_options, default_sort, and total_count to
empty product filters response.

Co-Authored-By: Claude <noreply@anthropic.com>
pawelekbyra and others added 18 commits July 7, 2026 14:30
Extend the F4 handler to the four other product.* events the admin
webhook endpoint now subscribes to (activated, archived, out_of_stock,
back_in_stock) — same serialized-product payload as created/updated/
deleted, so an archived or sold-out product also drops off the
storefront immediately instead of after the cache TTL.

Co-authored-by: Claude <noreply@anthropic.com>
Zasada na przyszłość dla F4 reszty (rynek/cena): handler najpierw,
subskrypcja w adminie potem — świadomie nie subskrybujemy *.
#6)

Zasada na przyszłość dla F4 reszty (rynek/cena): handler najpierw,
subskrypcja w adminie potem — świadomie nie subskrybujemy *.

Co-authored-by: Claude <noreply@anthropic.com>
Home, listing produktów i strona kategorii await'owały
resolveCurrency() (request do backendu) bezpośrednio w komponencie
strony, więc CAŁA strona — łącznie ze statyczną powłoką — czekała na
odpowiedź Render, zanim cokolwiek trafiało do przeglądarki. Przy
wolniejszych odpowiedziach backendu dawało to kilkusekundowy biały
ekran. Promise przekazywany jest teraz nierozwiązany do komponentów
już chronionych przez Suspense (FeaturedProducts, ProductListing) —
powłoka strony streamuje się od razu, waluta i produkty ładują się
równolegle w tle.
…agnosis-qevpga

Nie blokuj strony na resolveCurrency przed Suspense
Backend generates Active Storage variants lazily on first request.
Measured 12.5s to generate the xlarge (2000x2000) variant on Render's
constrained CPU vs 1.3s once cached — long enough to blow past Vercel
Image Optimization's fetch timeout and leave the product page showing
no image at all for the first visitor. Swapping the eagerly-loaded
main gallery image to the smaller large variant cuts generation time
substantially; xlarge stays in the lightbox where zooming is an
explicit, lower-priority user action.
…agnosis-qevpga

Zdjęcie na stronie produktu: preferuj large (720px) zamiast xlarge (2000px)
CountrySwitcher mieszał język z walutą w jednym dropdownie i linkował
wg usuniętego schematu URL /{country}/{locale}/... — 404 na wyborze
kraju plus wizualny duplikat "PL PL | PLN". Rozdzielenie na dwie
niezależne osie (Market vs Język) opisane w
sklepik/docs/plans/market-language-switcher.md; ten commit realizuje
kroki 0+1 (bezpieczne już dziś, bez zależności od drugiego rynku):
usunięcie zepsutego dropdownu, nowy LanguageSwitcher (next-intl
locale routing, niezależny od waluty/koszyka). Ten sam fix w
MobileMenu (panel "country" → "language").
…agnosis-qevpga

F11: zastąp zepsuty CountrySwitcher przełącznikiem języka
Deletion nie trafił do commita 34baab5 (git add -A z jawnymi
ścieżkami pominął te dwa pliki) — dogania to teraz.
Backend dostał nowy publiczny endpoint Store API (nazwa, logo, waluta/
locale) — nagłówek pokazuje teraz logo_url zamiast samej tekstowej
nazwy, gdy sklep je wgra (fallback na tekst bez logo), rozmiar
ograniczony do 40px wysokości bez wymuszonego cropu żeby nigdy nie
wyszło poza topbar. JSON-LD Organization też korzysta z realnego logo
z API zamiast statycznego env STORE_LOGO_URL (fallback zachowany).

@spree/sdk na npm nie ma jeszcze opublikowanej store.get() (istnieje
w monorepie, nie w wydanej wersji) — getStoreInfo() obchodzi to przez
udokumentowany escape hatch client.request(), opisane jako dług
techniczny do spłacenia po najbliższym wydaniu SDK.
…iagnosis-qevpga

F10: logo sklepu w nagłówku i JSON-LD
During Vercel prerendering, the backend API may be unreachable or return invalid
JSON, causing SyntaxError: Unexpected end of JSON input. This breaks the build
with: Export encountered an error on /[locale]/(storefront)/page.

Fix by adding try/catch blocks to all public data fetching functions in:
- getCategories, getCategory, getCategoryProducts
- getStoreInfo
- getProducts, getProduct, getProductFilters
- getMarkets, resolveMarket, getMarketCountries

These now gracefully return empty/default data on API errors instead of throwing,
allowing builds to complete even when the backend is unreachable. Errors are
logged for debugging but don't break the build.

The fix is defense-in-depth: in addition to these function-level handlers,
callers already had .catch() handlers in the layout, but those weren't
sufficient for JSON parsing errors within SDK calls.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjRTrtzJcaXUbcN7mf4Jbi
…r-bambls

Fix: Obsługa błędów API podczas budowania w Vercel
… self-signed certificate issue

Next.js was attempting to pre-render pages at build time, which required
fetching from the API. Vercel's Node.js build environment rejects self-signed
certificates with DEPTH_ZERO_SELF_SIGNED_CERT error.

Setting dynamic = 'force-dynamic' skips static generation and performs data
fetches at runtime instead (in the serverless function), avoiding the cert
issue. This is a temporary workaround until the backend has a valid HTTPS
certificate (Let's Encrypt with a proper domain).

Affected:
- src/app/[locale]/(storefront)/layout.tsx
- src/app/[locale]/(storefront)/page.tsx

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@pawelekbyra is attempting to deploy a commit to the Spree Commerce Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@pawelekbyra, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5c541eca-0f52-4a2b-bcf9-effe0569dea5

📥 Commits

Reviewing files that changed from the base of the PR and between 8c2bab5 and f3716d8.

📒 Files selected for processing (10)
  • next.config.ts
  • src/app/[locale]/(storefront)/c/[...permalink]/CategoryBanner.tsx
  • src/app/[locale]/(storefront)/page.tsx
  • src/app/api/revalidate/route.ts
  • src/lib/data/categories.ts
  • src/lib/data/countries.ts
  • src/lib/data/markets.ts
  • src/lib/data/products.ts
  • src/lib/data/store.ts
  • src/lib/webhooks/handlers.ts

Walkthrough

This PR migrates routing from [country]/[locale] to [locale]-only with a fixed default market, replaces the country switcher with a locale switcher, streams currency resolution via promises, fetches store branding info, hardens Spree data-fetching against misconfiguration, adds cron/revalidate/webhook API routes, and updates docs.

Changes

Storefront localization and platform update

Layer / File(s) Summary
Locale-only routing foundation
src/lib/utils/path.ts, src/lib/store.ts, src/lib/spree/middleware.ts, src/proxy.ts, src/app/sitemap.ts, src/app/robots.ts
Adds buildBasePath/updated extractBasePath, changes default country/locale to pl, rewrites middleware to locale-only prefixing, and refactors sitemap/robots to iterate locales instead of country+locale pairs.
Route pages, layouts, and metadata migration
src/app/[locale]/.../page.tsx, .../layout.tsx, src/lib/metadata/*
Removes country from route params and metadata generator signatures across checkout, account, product, category, and home pages; derives paths via buildBasePath(locale).
Language switcher UI and translations
src/components/layout/LanguageSwitcher.tsx, Header.tsx, MobileMenu.tsx, src/lib/utils/language-names.ts, messages/*.json
Adds a locale dropdown replacing the country switcher, with new selectLanguage translation keys in all locales.
Currency promise streaming
FeaturedProductsSection.tsx, FeaturedProducts.tsx, ProductListing.tsx
Switches from resolved currency prop to an unawaited currencyPromise, resolved inside Suspense boundaries.
Store branding and demo content cleanup
src/lib/data/store.ts, src/lib/seo.ts, Header.tsx, Footer.tsx, HeroSection.tsx, checkout layout.tsx
Adds getStoreInfo(), wires fetched logo into JSON-LD/Header, removes demo GitHub/Quickstart links.
Spree data-fetching resilience
src/lib/data/categories.ts, markets.ts, products.ts, src/lib/spree/config.ts, MediaGallery.tsx
Adds isSpreeConfigured() guards and error fallbacks to data helpers; adjusts main image URL preference.
Cron price sync, revalidation, and webhook expansion
src/app/api/cron/sync-eur-prices/route.ts, src/app/api/revalidate/route.ts, src/app/api/webhooks/spree/route.ts, src/lib/webhooks/handlers.ts, vercel.json
Adds an EUR price sync cron job, a manual revalidate endpoint, and extends webhook handling to product lifecycle events.
Documentation and configuration updates
AGENTS.md, CLAUDE.md, README.md, docs/*, .env.example, next.config.ts, .deploy-trigger
Adds/updates agent and deployment docs, environment variables, and image remote patterns.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant Middleware
  participant PathHelper as buildBasePath
  Browser->>Middleware: request /pl/products
  Middleware->>Middleware: check locale prefix
  Middleware->>Middleware: redirect to canonical unprefixed URL
  Browser->>Middleware: request /products
  Middleware->>Middleware: rewrite internally to /pl/products
  Middleware->>PathHelper: buildBasePath(locale)
  PathHelper-->>Middleware: base path for rendering
Loading
sequenceDiagram
  participant Spree as Spree Admin
  participant Webhook as /api/webhooks/spree
  participant Handler as handleProductChanged
  participant Cache as Next.js Cache
  participant Cron as /api/cron/sync-eur-prices
  participant NBP

  Spree->>Webhook: product.updated event
  Webhook->>Handler: dispatch event
  Handler->>Cache: revalidateTag(products, product:slug)
  Handler->>Cache: revalidatePath(/, layout)

  Cron->>NBP: fetch EUR/PLN rate
  Cron->>Spree: fetch PLN admin prices
  Cron->>Spree: bulk upsert EUR prices
Loading

Possibly related PRs

  • spree/storefront#118: Modifies the same FeaturedProductsSection.tsx/FeaturedProducts streaming wiring this PR further refactors into currency-promise based rendering.

Suggested reviewers: damianlegawiec

Poem

Country signs came down today, 🐇
Locales hop the URL way,
Currency streams while carrots wait,
Cron jobs sync the EUR rate,
A logo blooms where demo links once lay,
Sklepik grows brighter, hop hop hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: forcing the storefront to be dynamic, which is the central theme of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Nitpick comments (5)
CLAUDE.md (1)

42-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the new API route groups to the tree.

The structure diagram now stops at api/webhooks/, but this stack also adds api/cron/ and api/revalidate/. Agents using this file will miss those entry points unless the tree is updated.

Suggested update
 src/
 ├── app/                          # Next.js App Router
 │   ├── [locale]/                 # pl = default, no URL prefix; other locales get /{locale}
 │   │   ├── (checkout)/           # Checkout route group (minimal layout)
 │   │   └── (storefront)/         # Storefront route group (full layout)
-│   └── api/webhooks/             # Webhooks from the backend (transactional emails)
+│   └── api/
+│       ├── cron/                 # Scheduled price sync
+│       ├── revalidate/           # Manual cache busting
+│       └── webhooks/             # Webhooks from the backend (transactional emails)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 42 - 56, The repository tree in CLAUDE.md is missing
the new API route groups, so update the structure diagram under the app routes
to include api/cron/ and api/revalidate/ alongside api/webhooks/. Keep the
change limited to the documentation tree so readers can find the new entry
points without relying on outdated routing examples.
src/lib/utils/language-names.ts (1)

4-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use satisfies instead of a type annotation per coding guidelines.

As per coding guidelines, object literals should use satisfies for type checking. This preserves the literal type while still validating conformance to Record<Locale, string>.

♻️ Proposed refactor
-export const LANGUAGE_NAMES: Record<Locale, string> = {
+export const LANGUAGE_NAMES = {
   pl: "Polski",
   en: "English",
   de: "Deutsch",
   es: "Español",
   fr: "Français",
-};
+} satisfies Record<Locale, string>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils/language-names.ts` around lines 4 - 10, Replace the type
annotation on LANGUAGE_NAMES with a satisfies clause so the object literal keeps
its inferred literal types while still conforming to Record<Locale, string>.
Update the LANGUAGE_NAMES declaration in language-names.ts to use satisfies
instead of an explicit Record annotation, and keep the existing locale keys
unchanged.

Source: Coding guidelines

src/lib/webhooks/handlers.ts (1)

240-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit return type to handleProductChanged.

Per coding guidelines, all TypeScript functions should define explicit return types. handleProductChanged is missing : Promise<void>.

🔧 Proposed fix
-export async function handleProductChanged(event: WebhookEvent<Product>) {
+export async function handleProductChanged(
+  event: WebhookEvent<Product>,
+): Promise<void> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/webhooks/handlers.ts` at line 240, `handleProductChanged` is missing
an explicit TypeScript return type, so update the function signature to declare
`Promise<void>` and keep it consistent with the codebase’s typing guidelines.
Locate the `handleProductChanged` export in the webhooks handlers module and add
the return annotation directly on the function declaration without changing its
behavior.

Source: Coding guidelines

src/lib/data/categories.ts (1)

61-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared empty PaginatedResponse<Product> helper to avoid duplication.

emptyCategoryProductsResponse() here is identical to emptyProductListResponse() in src/lib/data/products.ts (lines 13–29) — same data: [], same meta fields, same as unknown as cast. Extract a single shared helper (e.g. src/lib/data/utils.ts) to keep the empty-response shape in one place.

♻️ Suggested shared helper
// src/lib/data/utils.ts
import type { PaginatedResponse, Product } from "`@spree/sdk`";

export function emptyProductListResponse(): PaginatedResponse<Product> {
  return {
    data: [],
    meta: {
      count: 0,
      pages: 0,
      page: 1,
      limit: 0,
      from: 0,
      to: 0,
      total_count: 0,
      in: 0,
      previous: null,
      next: null,
    },
  } as unknown as PaginatedResponse<Product>;
}

Then import and use it in both categories.ts and products.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/data/categories.ts` around lines 61 - 76, The empty
PaginatedResponse<Product> shape is duplicated in
emptyCategoryProductsResponse() and emptyProductListResponse(), so extract it
into one shared helper and reuse it from both categories.ts and products.ts.
Create a single utility function in a common module (for example, a data utils
helper) that returns the standard empty paginated product response with the same
meta fields and cast, then replace both local implementations with imports of
that helper to keep the response shape centralized.
src/lib/spree/middleware.ts (1)

81-88: 🧹 Nitpick | 🔵 Trivial

Consider a permanent (308) redirect for default-locale canonicalization.

NextResponse.redirect(url) defaults to 307 (temporary). Since this redirect enforces a canonical URL structure (removing the default-locale prefix), a 308 (permanent, method-preserving) would be better for SEO and browser caching.

♻️ Suggested change
-      const response = NextResponse.redirect(url);
+      const response = NextResponse.redirect(url, 308);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/spree/middleware.ts` around lines 81 - 88, The default-locale
canonicalization redirect in middleware should be permanent instead of
temporary. Update the redirect created in the logic that handles firstSegment
=== defaultLocale so that the NextResponse.redirect call uses a 308 status,
preserving the request method while signaling the canonical URL for SEO and
caching. Keep the existing URL rewrite behavior and cookie-setting flow in the
same middleware path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/deployment-vercel.md`:
- Around line 7-8: The deployment docs still reference the old env template
name, so update the wording in the deployment guide to match the checked-in
template filename used elsewhere. In the relevant deployment markdown, replace
the `.env.local.example` mention with `.env.example` and keep the guidance
consistent with the actual env-template file name referenced by the docs.

In `@docs/kierunek-frontu.md`:
- Around line 43-47: Update the `Aktualny etap` section in
`docs/kierunek-frontu.md` so it reflects the current repository state instead of
the stale 2026-07-06 snapshot. Adjust the date, move the already-implemented
cache revalidation/webhook/API-route work from “Przed nami” into the done status
where appropriate, and refresh the “Przed nami” bullet list to match the current
roadmap order; keep the wording aligned with the existing section structure and
the surrounding roadmap references.

In `@README.md`:
- Around line 44-47: The setup command in the README points to the wrong env
template filename, so update the onboarding instructions to copy from the
checked-in template used by the repo instead of the missing one. Fix the `cp`
command in the setup snippet to reference the actual template file, keeping the
rest of the install/dev steps unchanged.

In `@src/app/`[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx:
- Line 45: Update the confirm-payment page test route mocks to match the
locale-only routing shape. In renderPage and the related pathname/base-path
mocks, stop assuming the old country-plus-locale format and switch usePathname
and extractBasePath to values like "/en/confirm-payment/cart-1" and "/en". Keep
the assertions aligned with the current locale-only route handling so the tests
exercise the same behavior as the app.

In `@src/app/api/cron/sync-eur-prices/route.ts`:
- Around line 79-81: The sync flow in route.ts assumes
`AdminPricesResponse.meta.pages` always exists, but a successful response can
still have an unexpected body shape. Update the `body` handling in the cron sync
logic to safely read `meta.pages` with optional chaining and add a fallback when
metadata is missing, so the `prices.push(...)` and pagination update in the same
block do not throw from `body.meta` access.
- Around line 128-134: Remove the TEMP DEBUG artifact from the cron route
response and stop exposing secret metadata. In the sync-eur-prices route, update
the NextResponse.json error payload so it no longer includes apiKeyLength, and
delete the debug comment while keeping only the minimal boolean configuration
flags needed for diagnosing missing SPREE_API_URL or SPREE_ADMIN_SECRET_KEY.
Make the change in the route handler that checks backendUrl and apiKey so no
secret-derived length information is returned to callers.
- Line 42: Add timeouts to every upstream request in the EUR sync cron so slow
NBP or Spree calls fail fast instead of hanging. Update the fetches in
fetchEurPlnRate, fetchAllPlnPrices, and upsertEurPrices to pass an
AbortSignal.timeout(...) alongside the existing request options, and keep the
retry/error handling in the route aligned with aborted requests so the cron can
retry cleanly.
- Around line 140-158: The PLN→EUR price-sync workaround in syncEurPrices is
intentional commerce logic and should be tracked as technical debt. Add an entry
in docs/technical-debt.md referencing the sync-eur-prices route and the
fetchEurPlnRate/upsertEurPrices flow, briefly noting that this cron job converts
PLN prices to EUR using roundToPsychological99 so it is documented alongside the
other debt items.

In `@src/app/api/revalidate/route.ts`:
- Around line 36-37: The revalidation in the route handler currently uses
stale-while-revalidate behavior, so the catalog updates may not appear
immediately. Update the revalidation logic in the route that calls revalidateTag
for products and product-filters so it forces immediate freshness, using
updateTag or revalidateTag with an expire of 0 instead of the current max mode.
Keep the change within the revalidate endpoint and preserve the existing tag
names.

In `@src/components/layout/LanguageSwitcher.tsx`:
- Around line 30-33: The locales array in LanguageSwitcher currently includes
the default locale twice when it also appears in getPrefixedLocales(), which can
create duplicate dropdown entries and React key warnings. Update the locales
निर्माण in LanguageSwitcher to build the list from getDefaultLocale() and
getPrefixedLocales() while filtering out the default locale from the prefixed
results before mapping to Locale.

In `@src/lib/data/store.ts`:
- Around line 32-37: The cachedGetStore() helper tags store metadata with
"store", but the store update flow is not invalidating that tag, so updated
name/logo/currency/locales can remain stale. Update the store change handling
path(s) to also call revalidateTag("store", "max") alongside the existing
products and product-filters revalidation, using the relevant store update
action/component in src/lib/data/store.ts and any associated mutation handler.

In `@src/lib/metadata/products.ts`:
- Around line 18-24: `generateProductsMetadata` is hardcoding Polish SEO text
for every locale, so update it to read locale-aware `title` and `description`
(including the Open Graph copy) from the locale messages instead of returning
fixed strings. Add matching `products` metadata keys to the locale message files
and use the existing locale/canonical plumbing in `generateProductsMetadata` so
`/en/products` and other locales render the correct localized metadata.

In `@src/lib/spree/middleware.ts`:
- Around line 54-56: The locale comparison in the middleware is case-sensitive
because `firstSegment` is normalized to lowercase while
`getDefaultLocale()`/`defaultLocale` keeps the raw config value. Update the
locale initialization in `src/lib/spree/middleware.ts` so `defaultLocale` is
lowercased the same way `defaultCountry` is, and keep the redirect logic in the
middleware aligned with that normalized value. Use the `defaultLocale`,
`firstSegment`, and `supportedLocales` checks to verify `/PL/...` and `/pl/...`
behave consistently.

In `@src/lib/store.ts`:
- Around line 69-80: `getPrefixedLocales()` currently returns raw env values,
which can include the default locale and duplicate entries. Update
`getPrefixedLocales` in `src/lib/store.ts` to normalize, dedupe, and exclude
`getDefaultLocale()` so it remains the single source of truth for consumers like
`LanguageSwitcher` and `resolveLocales`. Keep the filtering centralized in this
helper rather than re-adding guards in each caller.

---

Nitpick comments:
In `@CLAUDE.md`:
- Around line 42-56: The repository tree in CLAUDE.md is missing the new API
route groups, so update the structure diagram under the app routes to include
api/cron/ and api/revalidate/ alongside api/webhooks/. Keep the change limited
to the documentation tree so readers can find the new entry points without
relying on outdated routing examples.

In `@src/lib/data/categories.ts`:
- Around line 61-76: The empty PaginatedResponse<Product> shape is duplicated in
emptyCategoryProductsResponse() and emptyProductListResponse(), so extract it
into one shared helper and reuse it from both categories.ts and products.ts.
Create a single utility function in a common module (for example, a data utils
helper) that returns the standard empty paginated product response with the same
meta fields and cast, then replace both local implementations with imports of
that helper to keep the response shape centralized.

In `@src/lib/spree/middleware.ts`:
- Around line 81-88: The default-locale canonicalization redirect in middleware
should be permanent instead of temporary. Update the redirect created in the
logic that handles firstSegment === defaultLocale so that the
NextResponse.redirect call uses a 308 status, preserving the request method
while signaling the canonical URL for SEO and caching. Keep the existing URL
rewrite behavior and cookie-setting flow in the same middleware path.

In `@src/lib/utils/language-names.ts`:
- Around line 4-10: Replace the type annotation on LANGUAGE_NAMES with a
satisfies clause so the object literal keeps its inferred literal types while
still conforming to Record<Locale, string>. Update the LANGUAGE_NAMES
declaration in language-names.ts to use satisfies instead of an explicit Record
annotation, and keep the existing locale keys unchanged.

In `@src/lib/webhooks/handlers.ts`:
- Line 240: `handleProductChanged` is missing an explicit TypeScript return
type, so update the function signature to declare `Promise<void>` and keep it
consistent with the codebase’s typing guidelines. Locate the
`handleProductChanged` export in the webhooks handlers module and add the return
annotation directly on the function declaration without changing its behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e9b33dc-6ff3-4a16-8492-81bda0a902a1

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad5a07 and 8c2bab5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (78)
  • .deploy-trigger
  • .env.example
  • AGENTS.md
  • CLAUDE.md
  • README.md
  • docs/README.md
  • docs/deployment-vercel.md
  • docs/kierunek-frontu.md
  • docs/technical-debt.md
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/pl.json
  • next.config.ts
  • src/app/[country]/[locale]/(storefront)/page.tsx
  • src/app/[locale]/(checkout)/checkout/[id]/CheckoutPageContent.tsx
  • src/app/[locale]/(checkout)/checkout/[id]/CheckoutSidebar.tsx
  • src/app/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[locale]/(checkout)/confirm-payment/[id]/page.tsx
  • src/app/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx
  • src/app/[locale]/(checkout)/layout.tsx
  • src/app/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[locale]/(storefront)/account/addresses/page.tsx
  • src/app/[locale]/(storefront)/account/credit-cards/page.tsx
  • src/app/[locale]/(storefront)/account/forgot-password/page.tsx
  • src/app/[locale]/(storefront)/account/gift-cards/page.tsx
  • src/app/[locale]/(storefront)/account/layout.tsx
  • src/app/[locale]/(storefront)/account/orders/[id]/page.tsx
  • src/app/[locale]/(storefront)/account/orders/page.tsx
  • src/app/[locale]/(storefront)/account/page.tsx
  • src/app/[locale]/(storefront)/account/profile/page.tsx
  • src/app/[locale]/(storefront)/account/register/page.tsx
  • src/app/[locale]/(storefront)/account/reset-password/page.tsx
  • src/app/[locale]/(storefront)/c/[...permalink]/CategoryBanner.tsx
  • src/app/[locale]/(storefront)/c/[...permalink]/page.tsx
  • src/app/[locale]/(storefront)/cart/page.tsx
  • src/app/[locale]/(storefront)/layout.tsx
  • src/app/[locale]/(storefront)/page.tsx
  • src/app/[locale]/(storefront)/policies/[slug]/page.tsx
  • src/app/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/app/[locale]/(storefront)/products/[slug]/page.tsx
  • src/app/[locale]/(storefront)/products/page.tsx
  • src/app/[locale]/layout.tsx
  • src/app/api/cron/sync-eur-prices/route.ts
  • src/app/api/revalidate/route.ts
  • src/app/api/webhooks/spree/route.ts
  • src/app/robots.ts
  • src/app/sitemap.ts
  • src/components/home/FeaturedProductsSection.tsx
  • src/components/home/HeroSection.tsx
  • src/components/layout/CountrySwitcher.tsx
  • src/components/layout/Footer.tsx
  • src/components/layout/Header.tsx
  • src/components/layout/LanguageSwitcher.tsx
  • src/components/layout/MobileMenu.tsx
  • src/components/products/FeaturedProducts.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductListing.tsx
  • src/hooks/useCountrySwitch.ts
  • src/lib/data/categories.ts
  • src/lib/data/markets.ts
  • src/lib/data/products.ts
  • src/lib/data/store.ts
  • src/lib/metadata/category.ts
  • src/lib/metadata/home.ts
  • src/lib/metadata/product.ts
  • src/lib/metadata/products.ts
  • src/lib/seo.ts
  • src/lib/spree/config.ts
  • src/lib/spree/middleware.ts
  • src/lib/store.ts
  • src/lib/utils/language-names.ts
  • src/lib/utils/path.ts
  • src/lib/webhooks/__tests__/handlers.test.ts
  • src/lib/webhooks/handlers.ts
  • src/proxy.ts
  • vercel.json
💤 Files with no reviewable changes (7)
  • src/components/layout/CountrySwitcher.tsx
  • src/app/[locale]/(storefront)/policies/[slug]/page.tsx
  • src/hooks/useCountrySwitch.ts
  • src/app/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[locale]/(checkout)/confirm-payment/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/page.tsx
  • src/components/home/HeroSection.tsx

Comment thread docs/deployment-vercel.md
Comment on lines +7 to +8
Prawdziwe wartości ustawiamy wyłącznie w Vercel (Project Settings → Environment Variables) albo lokalnie w `.env.local` (nie commitować). W repo jest `.env.local.example`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the same env-template filename here too.

This page still says .env.local.example, but the checked-in template is .env.example. Align the filenames across the docs so readers don't chase a missing file.

Suggested fix
-W repo jest `.env.local.example`.
+W repo jest `.env.example`.
📝 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
Prawdziwe wartości ustawiamy wyłącznie w Vercel (Project Settings → Environment Variables) albo lokalnie w `.env.local` (nie commitować). W repo jest `.env.local.example`.
Prawdziwe wartości ustawiamy wyłącznie w Vercel (Project Settings → Environment Variables) albo lokalnie w `.env.local` (nie commitować). W repo jest `.env.example`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/deployment-vercel.md` around lines 7 - 8, The deployment docs still
reference the old env template name, so update the wording in the deployment
guide to match the checked-in template filename used elsewhere. In the relevant
deployment markdown, replace the `.env.local.example` mention with
`.env.example` and keep the guidance consistent with the actual env-template
file name referenced by the docs.

Comment thread docs/kierunek-frontu.md
Comment on lines +43 to +47
## Aktualny etap (2026-07-06)

Zrobione: rebranding podstawowy (nazwa, layout bez elementów demo), polski domyślny locale bez prefiksu URL, katalog i strony produktów działają na realnych danych z backendu.

Przed nami (kolejność wg `sklepik/docs/roadmap.md`): rewalidacja cache po zmianach w adminie (F4), trwała idempotencja webhooków e-mail (F6), potem pełny branding premium, strony informacyjne i płatności w Fazie 2.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Refresh the status block to match the current stack.

Aktualny etap is still pinned to 2026-07-06 and keeps cache revalidation in the “Przed nami” list, but this stack already adds the revalidate/webhook/API-route work. Please update the date and the done/next bullets so the guide reflects the current repository state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/kierunek-frontu.md` around lines 43 - 47, Update the `Aktualny etap`
section in `docs/kierunek-frontu.md` so it reflects the current repository state
instead of the stale 2026-07-06 snapshot. Adjust the date, move the
already-implemented cache revalidation/webhook/API-route work from “Przed nami”
into the done status where appropriate, and refresh the “Przed nami” bullet list
to match the current roadmap order; keep the wording aligned with the existing
section structure and the surrounding roadmap references.

Comment thread README.md
Comment on lines 44 to +47
```bash
brew install cloudflared
cloudflared tunnel --url http://localhost:3001
```

Use the tunnel URL as the webhook endpoint URL in Spree Admin.

No `RESEND_API_KEY` needed in dev — emails are rendered to HTML files in `.next/emails/` with a `file://` link logged to the console.

### Email Templates

Templates are in `src/lib/emails/` as React components:

| Template | Event | Description |
|----------|-------|-------------|
| `order-confirmation.tsx` | `order.completed` | Order placed with items, totals, addresses |
| `order-canceled.tsx` | `order.canceled` | Cancellation notice |
| `shipment-shipped.tsx` | `order.shipped` | Shipping notification with tracking link |
| `password-reset.tsx` | `customer.password_reset_requested` | Password reset link |

### Previewing Templates

Run the storefront in development (`npm run dev`) and visit [http://localhost:3001/dev/emails](http://localhost:3001/dev/emails). Each template is rendered with sample data using `@react-email/render`; the route is gated to non-production environments.

### How It Works

```
Spree Backend → Webhook POST → /api/webhooks/spree → render email → send via Resend
(signed HMAC) (signature verified) (react-email) (or write to disk in dev)
```

The webhook route handler (`src/app/api/webhooks/spree/route.ts`) uses `createWebhookHandler` from `src/lib/spree/webhooks` — signature verification and event routing are handled automatically.

## Deployment

### Vercel

The easiest way to deploy is using [Vercel](https://vercel.com/new):

1. Push your code to GitHub
2. Import the repository in Vercel
3. Add environment variables:
- `SPREE_API_URL` and `SPREE_PUBLISHABLE_KEY` (required)
- `SPREE_WEBHOOK_SECRET`, `RESEND_API_KEY`, `EMAIL_FROM` (for transactional emails)
- `GTM_ID` (optional — Google Tag Manager)
- `SENTRY_DSN`, `SENTRY_ORG`, `SENTRY_PROJECT`, `SENTRY_AUTH_TOKEN` (optional — for error tracking with readable stack traces)
4. Deploy

### Docker

A multi-stage `Dockerfile` is included at the repo root. It uses Next.js standalone output to produce a small (~240 MB) image based on `node:22-alpine`, runs as a non-root user, and exposes port `3001`.

> **Note:** `SPREE_API_URL` and `SPREE_PUBLISHABLE_KEY` are required at **build time** because the storefront prerenders pages against the Spree API. Point them at a Spree instance reachable from wherever you run `docker build` (hosted Spree, tunnel, or `host.docker.internal` for a local backend on Docker Desktop).

**Build:**

```bash
docker build \
--build-arg SPREE_API_URL=https://your-spree.example.com \
--build-arg SPREE_PUBLISHABLE_KEY=your_publishable_key \
-t spree-storefront .
```

**Run:**

```bash
docker run -p 3001:3001 --env-file .env.local spree-storefront
```

**Optional — Sentry source map upload at build time:**

`SENTRY_AUTH_TOKEN` is mounted via a BuildKit secret so it never lands in image layers or the build cache. Other Sentry vars are passed as regular build args.

```bash
SENTRY_AUTH_TOKEN=... docker build \
--build-arg SPREE_API_URL=... \
--build-arg SPREE_PUBLISHABLE_KEY=... \
--build-arg SENTRY_DSN=... \
--build-arg SENTRY_ORG=... \
--build-arg SENTRY_PROJECT=... \
--secret id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
-t spree-storefront .
```

**Building against a local Spree backend** (Docker Desktop on macOS/Windows):

```bash
docker build \
--add-host=host.docker.internal:host-gateway \
--build-arg SPREE_API_URL=http://host.docker.internal:3000 \
--build-arg SPREE_PUBLISHABLE_KEY=your_publishable_key \
-t spree-storefront .
npm install
cp .env.local.example .env.local # SPREE_API_URL, SPREE_PUBLISHABLE_KEY
npm run dev # http://localhost:3001

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the env-template filename.

cp .env.local.example .env.local points at a file that isn't checked in here; the repo ships .env.example. Please align the setup command with the actual template so onboarding doesn't fail.

Suggested fix
-cp .env.local.example .env.local   # SPREE_API_URL, SPREE_PUBLISHABLE_KEY
+cp .env.example .env.local         # SPREE_API_URL, SPREE_PUBLISHABLE_KEY
📝 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
```bash
brew install cloudflared
cloudflared tunnel --url http://localhost:3001
```
Use the tunnel URL as the webhook endpoint URL in Spree Admin.
No `RESEND_API_KEY` needed in dev — emails are rendered to HTML files in `.next/emails/` with a `file://` link logged to the console.
### Email Templates
Templates are in `src/lib/emails/` as React components:
| Template | Event | Description |
|----------|-------|-------------|
| `order-confirmation.tsx` | `order.completed` | Order placed with items, totals, addresses |
| `order-canceled.tsx` | `order.canceled` | Cancellation notice |
| `shipment-shipped.tsx` | `order.shipped` | Shipping notification with tracking link |
| `password-reset.tsx` | `customer.password_reset_requested` | Password reset link |
### Previewing Templates
Run the storefront in development (`npm run dev`) and visit [http://localhost:3001/dev/emails](http://localhost:3001/dev/emails). Each template is rendered with sample data using `@react-email/render`; the route is gated to non-production environments.
### How It Works
```
Spree Backend → Webhook POST → /api/webhooks/spree → render email → send via Resend
(signed HMAC) (signature verified) (react-email) (or write to disk in dev)
```
The webhook route handler (`src/app/api/webhooks/spree/route.ts`) uses `createWebhookHandler` from `src/lib/spree/webhooks` — signature verification and event routing are handled automatically.
## Deployment
### Vercel
The easiest way to deploy is using [Vercel](https://vercel.com/new):
1. Push your code to GitHub
2. Import the repository in Vercel
3. Add environment variables:
- `SPREE_API_URL` and `SPREE_PUBLISHABLE_KEY` (required)
- `SPREE_WEBHOOK_SECRET`, `RESEND_API_KEY`, `EMAIL_FROM` (for transactional emails)
- `GTM_ID` (optional — Google Tag Manager)
- `SENTRY_DSN`, `SENTRY_ORG`, `SENTRY_PROJECT`, `SENTRY_AUTH_TOKEN` (optional — for error tracking with readable stack traces)
4. Deploy
### Docker
A multi-stage `Dockerfile` is included at the repo root. It uses Next.js standalone output to produce a small (~240 MB) image based on `node:22-alpine`, runs as a non-root user, and exposes port `3001`.
> **Note:** `SPREE_API_URL` and `SPREE_PUBLISHABLE_KEY` are required at **build time** because the storefront prerenders pages against the Spree API. Point them at a Spree instance reachable from wherever you run `docker build` (hosted Spree, tunnel, or `host.docker.internal` for a local backend on Docker Desktop).
**Build:**
```bash
docker build \
--build-arg SPREE_API_URL=https://your-spree.example.com \
--build-arg SPREE_PUBLISHABLE_KEY=your_publishable_key \
-t spree-storefront .
```
**Run:**
```bash
docker run -p 3001:3001 --env-file .env.local spree-storefront
```
**Optional — Sentry source map upload at build time:**
`SENTRY_AUTH_TOKEN` is mounted via a BuildKit secret so it never lands in image layers or the build cache. Other Sentry vars are passed as regular build args.
```bash
SENTRY_AUTH_TOKEN=... docker build \
--build-arg SPREE_API_URL=... \
--build-arg SPREE_PUBLISHABLE_KEY=... \
--build-arg SENTRY_DSN=... \
--build-arg SENTRY_ORG=... \
--build-arg SENTRY_PROJECT=... \
--secret id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
-t spree-storefront .
```
**Building against a local Spree backend** (Docker Desktop on macOS/Windows):
```bash
docker build \
--add-host=host.docker.internal:host-gateway \
--build-arg SPREE_API_URL=http://host.docker.internal:3000 \
--build-arg SPREE_PUBLISHABLE_KEY=your_publishable_key \
-t spree-storefront .
npm install
cp .env.local.example .env.local # SPREE_API_URL, SPREE_PUBLISHABLE_KEY
npm run dev # http://localhost:3001
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 44 - 47, The setup command in the README points to
the wrong env template filename, so update the onboarding instructions to copy
from the checked-in template used by the repo instead of the missing one. Fix
the `cp` command in the setup snippet to reference the actual template file,
keeping the rest of the install/dev steps unchanged.

const mockConfirm = vi.mocked(confirmPaymentAndCompleteCart);

function renderPage(params = { id: "cart-1", country: "us", locale: "en" }) {
function renderPage(params = { id: "cart-1", locale: "en" }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if ConfirmPaymentPage uses usePathname for routing logic
rg -nP 'usePathname' src/app/\[locale\]/\(checkout\)/confirm-payment/\[id\]/page.tsx

Repository: spree/storefront

Length of output: 268


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the page implementation and the test mock around the mentioned lines.
sed -n '1,220p' src/app/\[locale\]/\(checkout\)/confirm-payment/\[id\]/page.tsx
printf '\n--- TEST ---\n'
sed -n '1,140p' src/app/\[locale\]/\(checkout\)/confirm-payment/__tests__/page.test.tsx

Repository: spree/storefront

Length of output: 6606


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect base-path extraction and any related routing tests/usage.
sed -n '1,220p' src/lib/utils/path.ts
printf '\n--- RELATED MATCHES ---\n'
rg -n "extractBasePath\\(|usePathname\\(|/us/en/confirm-payment|/en/confirm-payment" src/app src/lib -g '!**/*.map'

Repository: spree/storefront

Length of output: 3673


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check which locale prefixes are currently supported.
sed -n '1,220p' src/lib/store.ts
printf '\n--- locale usages ---\n'
rg -n "getDefaultLocale|getPrefixedLocales|prefixedLocales|defaultLocale" src/lib src/app -g '!**/*.map'

Repository: spree/storefront

Length of output: 5912


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the rest of the test to see the expected redirect paths and the mocked base-path behavior.
sed -n '140,260p' src/app/\[locale\]/\(checkout\)/confirm-payment/__tests__/page.test.tsx

Repository: spree/storefront

Length of output: 1743


Update the route mocks to the locale-only shape.

usePathname still returns "/us/en/confirm-payment/cart-1", and the mocked extractBasePath still assumes two path segments. Switch both to the current locale-only format (for example "/en/confirm-payment/cart-1" and "/en"), or the redirect assertions will keep exercising the old country/locale routing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`[locale]/(checkout)/confirm-payment/__tests__/page.test.tsx at line
45, Update the confirm-payment page test route mocks to match the locale-only
routing shape. In renderPage and the related pathname/base-path mocks, stop
assuming the old country-plus-locale format and switch usePathname and
extractBasePath to values like "/en/confirm-payment/cart-1" and "/en". Keep the
assertions aligned with the current locale-only route handling so the tests
exercise the same behavior as the app.

}

async function fetchEurPlnRate(): Promise<number> {
const res = await fetch(NBP_RATE_URL, { cache: "no-store" });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the project's declared Node.js version
fd -e nvmrc -e node-version --exec cat 2>/dev/null || cat .tool-versions 2>/dev/null | grep nodejs || rg "node" package.json -A2 | head -5

Repository: spree/storefront

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Node/version config ==\n'
fd -H -a 'package.json' . .nvmrc .node-version .tool-versions 2>/dev/null || true
printf '\n-- package.json (top relevant lines) --\n'
if [ -f package.json ]; then sed -n '1,120p' package.json; fi
for f in .nvmrc .node-version .tool-versions; do
  if [ -f "$f" ]; then printf '\n-- %s --\n' "$f"; cat "$f"; fi
done

printf '\n== route.ts around the fetches ==\n'
nl -ba src/app/api/cron/sync-eur-prices/route.ts | sed -n '1,170p'

printf '\n== technical debt docs presence ==\n'
fd -a 'technical-debt.md' docs . 2>/dev/null || true
if [ -f docs/technical-debt.md ]; then
  printf '\n-- docs/technical-debt.md matches --\n'
  rg -n 'EUR|price|conversion|cron|NBP|Spree' docs/technical-debt.md || true
fi

Repository: spree/storefront

Length of output: 2527


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== route.ts fetch sites ==\n'
sed -n '1,180p' src/app/api/cron/sync-eur-prices/route.ts

printf '\n== technical debt doc ==\n'
if [ -f docs/technical-debt.md ]; then
  sed -n '1,220p' docs/technical-debt.md
else
  echo 'docs/technical-debt.md not found'
fi

printf '\n== AbortSignal.timeout runtime check ==\n'
node - <<'JS'
console.log(typeof AbortSignal.timeout)
JS

Repository: spree/storefront

Length of output: 15530


Add timeouts to all upstream fetches. fetchEurPlnRate, fetchAllPlnPrices, and upsertEurPrices can hang until the function times out if NBP or Spree slows down. Add AbortSignal.timeout(...) to each request so the cron fails fast and retries cleanly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/cron/sync-eur-prices/route.ts` at line 42, Add timeouts to every
upstream request in the EUR sync cron so slow NBP or Spree calls fail fast
instead of hanging. Update the fetches in fetchEurPlnRate, fetchAllPlnPrices,
and upsertEurPrices to pass an AbortSignal.timeout(...) alongside the existing
request options, and keep the retry/error handling in the route aligned with
aborted requests so the cron can retry cleanly.

Comment on lines +30 to +33
const locales = [
getDefaultLocale() as Locale,
...getPrefixedLocales().map((l) => l as Locale),
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Deduplicate the locales array to prevent duplicate dropdown entries.

If the default locale is also listed in NEXT_PUBLIC_PREFIXED_LOCALES, the array will contain duplicates, producing duplicate dropdown items and React key warnings. Filter out the default locale from the prefixed list:

🛡️ Proposed fix
 const locales = [
   getDefaultLocale() as Locale,
-  ...getPrefixedLocales().map((l) => l as Locale),
+  ...getPrefixedLocales()
+    .filter((l) => l !== getDefaultLocale())
+    .map((l) => l as Locale),
 ];
📝 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
const locales = [
getDefaultLocale() as Locale,
...getPrefixedLocales().map((l) => l as Locale),
];
const locales = [
getDefaultLocale() as Locale,
...getPrefixedLocales()
.filter((l) => l !== getDefaultLocale())
.map((l) => l as Locale),
];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/layout/LanguageSwitcher.tsx` around lines 30 - 33, The locales
array in LanguageSwitcher currently includes the default locale twice when it
also appears in getPrefixedLocales(), which can create duplicate dropdown
entries and React key warnings. Update the locales निर्माण in LanguageSwitcher
to build the list from getDefaultLocale() and getPrefixedLocales() while
filtering out the default locale from the prefixed results before mapping to
Locale.

Comment thread src/lib/data/store.ts
Comment on lines +32 to +37
async function cachedGetStore(): Promise<StoreInfo> {
"use cache: remote";
cacheLife("hours");
cacheTag("store");
return getClient().request<StoreInfo>("GET", "/store");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Check Next.js version
jq '.dependencies.next' package.json

# 2. Search for "use cache" in Next.js source
rg -l '"use cache' node_modules/next/dist/ 2>/dev/null | head -5

# 3. Search for getSpreeClient vs getClient
rg -n 'getSpreeClient|export.*getClient' src/lib/spree/ --type=ts

# 4. Check cacheLife profiles and useCache in next.config.ts
rg -n 'cacheLife|useCache|experimental' next.config.ts 2>/dev/null

# 5. Search for revalidateTag("store") in the codebase
rg -n 'revalidateTag.*store' src/ --type=ts

# 6. Search for request method in spree client module
rg -n 'request' src/lib/spree/ --type=ts | head -20

Repository: spree/storefront

Length of output: 1961


🌐 Web query:

Next.js 16 "use cache: remote" directive documentation

💡 Result:

In Next.js 16, the 'use cache: remote' directive is a feature designed to enable durable, shared caching for data and components in dynamic contexts [1][2]. Key details regarding 'use cache: remote' include: Purpose and Use Case Standard 'use cache' typically relies on in-memory storage, which is not shared across different server instances in distributed or serverless environments [3]. The 'use cache: remote' directive allows you to specify that cached output should be stored in a remote, persistent cache (such as Redis) [1][3]. This ensures cache hits are shared across all server instances, reducing database load and improving performance in dynamic scenarios [1][3]. Dynamic Contexts A primary advantage of 'use cache: remote' is its ability to operate within dynamic contexts where standard 'use cache' would not function [2]. This includes code executed after calling dynamic functions such as: - await connection [2] - await cookies [2] - await headers [2] Configuration To utilize this directive, you must enable the cacheComponents flag in your next.config.ts file [2]: // next.config.ts const nextConfig = { experimental: { cacheComponents: true, }, }; export default nextConfig; Implementation You can add 'use cache: remote' to the top of a file or within a specific function, depending on the desired scope of the caching [3]. // Example usage async function getSharedData { 'use cache: remote'; // Logic that depends on dynamic contexts, e.g., cookies const cookieStore = await cookies; //... } Trade-offs While 'use cache: remote' provides shared, persistent storage, it introduces infrastructure requirements (such as a configured cache handler) and potential network latency during cache lookups compared to local in-memory caching [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== package.json next version ==\n'
jq -r '.dependencies.next // .devDependencies.next // empty' package.json

printf '\n== next.config.ts cache settings ==\n'
if [ -f next.config.ts ]; then
  cat -n next.config.ts | sed -n '1,120p'
fi

printf '\n== src/lib/spree files ==\n'
git ls-files 'src/lib/spree/**/*.ts' 'src/lib/spree/*.ts'

printf '\n== src/lib/spree/config.ts ==\n'
cat -n src/lib/spree/config.ts | sed -n '1,220p'

printf '\n== src/lib/spree/index.ts ==\n'
cat -n src/lib/spree/index.ts | sed -n '1,80p'

printf '\n== src/lib/data/store.ts ==\n'
cat -n src/lib/data/store.ts | sed -n '1,220p'

printf '\n== store tag revalidation / webhooks ==\n'
rg -n 'revalidateTag\\(|cacheTag\\(|store' src/lib src/app src -g '!**/*.map' -g '!node_modules/**'

printf '\n== request method / path handling in spree client ==\n'
rg -n 'request\\s*\\(' src/lib/spree -g '*.ts'

Repository: spree/storefront

Length of output: 9533


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Next cacheLife typings / built-in profiles ==\n'
rg -n 'cacheLife|hours|minutes|days|cacheComponents|use cache: remote' node_modules/next -g '*.{ts,js,mjs,d.ts}' | head -80

printf '\n== Spree SDK request implementation ==\n'
rg -n 'class Client|request\\(|baseUrl|/api/v3|store' node_modules/@spree/sdk -g '*.{ts,js,mjs,d.ts}' | head -120

printf '\n== store revalidation / webhooks in app and src ==\n'
rg -n 'revalidateTag\\("store"|cacheTag\\("store"|store\\b' src app -g '!**/*.map' -g '!node_modules/**'

printf '\n== webhook route / handler ==\n'
rg -n 'webhooks|revalidateTag|waitUntil|POST\\(' src/app src/lib/spree -g '*.{ts,tsx}' -g '!**/*.map'

Repository: spree/storefront

Length of output: 10425


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Spree SDK client and request routing ==\n'
rg -n 'class Client|function createClient|request\\s*\\(|baseUrl|/api/v3|store' node_modules/@spree/sdk -g '*.{ts,js,mjs,d.ts}' | head -200

printf '\n== store tag invalidation and webhook handlers ==\n'
rg -n 'revalidateTag\\("store"|cacheTag\\("store"|revalidateTag\\(|cacheTag\\(|store\\b' src app -g '!**/*.map' -g '!node_modules/**'

printf '\n== any getSpreeClient symbol ==\n'
rg -n 'getSpreeClient' src node_modules/@spree/sdk -g '*.{ts,js,mjs,d.ts}' || true

Repository: spree/storefront

Length of output: 369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== SDK package contents ==\n'
find node_modules/@spree/sdk -maxdepth 3 -type f \( -name '*.ts' -o -name '*.js' -o -name '*.mjs' -o -name '*.d.ts' \) | sed -n '1,80p'

printf '\n== request implementation and path handling ==\n'
rg -n -F 'request(' node_modules/@spree/sdk | head -80
rg -n -F '/api/v3' node_modules/@spree/sdk | head -80
rg -n -F 'store' node_modules/@spree/sdk | head -120

printf '\n== store revalidation in app/src ==\n'
rg -n -F 'revalidateTag("store")' src app || true
rg -n -F 'cacheTag("store")' src app || true
rg -n -F 'revalidateTag(' src app | sed -n '1,120p'

printf '\n== webhook handlers ==\n'
rg -n -F 'webhook' src app | sed -n '1,120p'

Repository: spree/storefront

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== revalidateTag / cacheTag exact matches ==\n'
rg -n -F 'revalidateTag("store")' src app || true
rg -n -F 'cacheTag("store")' src app || true

printf '\n== webhook / revalidation handlers ==\n'
rg -n -F 'revalidateTag(' src app | sed -n '1,120p'
rg -n -F 'webhook' src app | sed -n '1,120p'

printf '\n== Spree webhooks module ==\n'
cat -n src/lib/spree/webhooks.ts | sed -n '1,240p'

Repository: spree/storefront

Length of output: 813


Revalidate the store cache tag on store updates
src/lib/data/store.ts caches store metadata for an hour, but the current revalidation flow only clears products and product-filters. Add revalidateTag("store", "max") wherever store changes are handled so name/logo/currency/locales don’t stay stale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/data/store.ts` around lines 32 - 37, The cachedGetStore() helper tags
store metadata with "store", but the store update flow is not invalidating that
tag, so updated name/logo/currency/locales can remain stale. Update the store
change handling path(s) to also call revalidateTag("store", "max") alongside the
existing products and product-filters revalidation, using the relevant store
update action/component in src/lib/data/store.ts and any associated mutation
handler.

Source: Coding guidelines

Comment on lines 18 to +24
return {
title: "Products",
description: "Browse our full collection of products.",
title: "Produkty",
description: "Przeglądaj naszą pełną kolekcję produktów.",
...(canonicalUrl ? { alternates: { canonical: canonicalUrl } } : {}),
openGraph: {
title: "Products",
description: "Browse our full collection of products.",
title: "Produkty",
description: "Przeglądaj naszą pełną kolekcję produktów.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## File outline\n'
ast-grep outline src/lib/metadata/products.ts --view expanded || true

printf '\n## Relevant file excerpt\n'
cat -n src/lib/metadata/products.ts | sed -n '1,220p'

printf '\n## Search for metadata generators and locale-aware copy\n'
rg -n "generate.*Metadata|openGraph|title:|description:" src/lib/metadata src -g '!**/*.map' || true

printf '\n## Search for products translation namespace usage\n'
rg -n "products|useTranslations|getTranslations|next-intl|i18n" src -g '!**/*.map' || true

Repository: spree/storefront

Length of output: 39620


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Find translation files\n'
fd -a 'products.*' src app . || true
fd -a 'en.*' src app . || true
fd -a 'pl.*' src app . || true
fd -a '*messages*' src app . || true

printf '\n## Search for products namespace keys in locale files\n'
rg -n '"products"|products:' src app -g '*.{json,ts,tsx,yml,yaml}' || true

printf '\n## Inspect i18n request/config and locale handling\n'
cat -n src/i18n/request.ts | sed -n '1,220p'
cat -n src/lib/spree/middleware.ts | sed -n '1,180p'

Repository: spree/storefront

Length of output: 14801


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from pathlib import Path

for path in sorted(Path("messages").glob("*.json")):
    data = json.loads(path.read_text(encoding="utf-8"))
    products = data.get("products")
    print(f"\n## {path}")
    if isinstance(products, dict):
        for key in ["title", "description", "metaTitle", "metaDescription", "seoTitle", "seoDescription"]:
            if key in products:
                print(f"{key}: {products[key]}")
    else:
        print("products namespace not found")
PY

Repository: spree/storefront

Length of output: 378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from pathlib import Path

for path in sorted(Path("messages").glob("*.json")):
    data = json.loads(path.read_text(encoding="utf-8"))
    products = data.get("products", {})
    print(f"\n## {path.name}")
    if isinstance(products, dict):
        print(json.dumps(products, ensure_ascii=False, indent=2))
    else:
        print("products namespace missing or not an object")
PY

Repository: spree/storefront

Length of output: 14651


Localize the products metadata copy.

generateProductsMetadata returns fixed Polish title/description (and Open Graph copies) for every locale. Non-default locales like /en/products will keep Polish SEO text while the canonical URL is locale-specific. Add locale-aware metadata strings for this page and include matching products metadata keys in the locale messages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/metadata/products.ts` around lines 18 - 24,
`generateProductsMetadata` is hardcoding Polish SEO text for every locale, so
update it to read locale-aware `title` and `description` (including the Open
Graph copy) from the locale messages instead of returning fixed strings. Add
matching `products` metadata keys to the locale message files and use the
existing locale/canonical plumbing in `generateProductsMetadata` so
`/en/products` and other locales render the correct localized metadata.

Comment on lines +54 to +56
const defaultCountry = config.defaultCountry ?? "pl";
const defaultLocale = config.defaultLocale ?? "pl";
const supportedLocales = config.supportedLocales ?? ["en"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Lowercase defaultLocale to match the lowercased firstSegment comparison.

firstSegment is lowercased at line 77, but defaultLocale retains the raw config value. If NEXT_PUBLIC_DEFAULT_LOCALE is set to an uppercase value (e.g. "PL"), the equality check at line 81 (firstSegment === defaultLocale) will fail — "pl" !== "PL" — causing /PL/products to fall through to the supportedLocales branch or the rewrite branch instead of redirecting to the canonical unprefixed URL. getDefaultCountry() already lowercases its result; getDefaultLocale() does not, so the mismatch is latent.

🔧 Proposed fix
-  const defaultLocale = config.defaultLocale ?? "pl";
+  const defaultLocale = (config.defaultLocale ?? "pl").toLowerCase();
📝 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
const defaultCountry = config.defaultCountry ?? "pl";
const defaultLocale = config.defaultLocale ?? "pl";
const supportedLocales = config.supportedLocales ?? ["en"];
const defaultCountry = config.defaultCountry ?? "pl";
const defaultLocale = (config.defaultLocale ?? "pl").toLowerCase();
const supportedLocales = config.supportedLocales ?? ["en"];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/spree/middleware.ts` around lines 54 - 56, The locale comparison in
the middleware is case-sensitive because `firstSegment` is normalized to
lowercase while `getDefaultLocale()`/`defaultLocale` keeps the raw config value.
Update the locale initialization in `src/lib/spree/middleware.ts` so
`defaultLocale` is lowercased the same way `defaultCountry` is, and keep the
redirect logic in the middleware aligned with that normalized value. Use the
`defaultLocale`, `firstSegment`, and `supportedLocales` checks to verify
`/PL/...` and `/pl/...` behave consistently.

Comment thread src/lib/store.ts
Comment on lines +69 to 80
/**
* Locale codes shown with a URL prefix (e.g. 'en' -> /en/...). Kept in sync
* with the middleware's `supportedLocales` default (see proxy.ts) — this is
* the single source of truth for "which locales does this store serve",
* consumed by both the middleware and the sitemap generator.
*/
export function getPrefixedLocales(): string[] {
return (process.env.NEXT_PUBLIC_PREFIXED_LOCALES || "en")
.split(",")
.map((l) => l.trim().toLowerCase())
.filter(Boolean);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

getPrefixedLocales() should guard against overlapping with the default locale (and dedupe).

This function's own docstring calls it "the single source of truth for ... which locales does this store serve", consumed by middleware, sitemap, and the language switcher. If NEXT_PUBLIC_PREFIXED_LOCALES is ever configured to include the default locale (e.g. "pl,en" while NEXT_PUBLIC_DEFAULT_LOCALE=pl), or contains a duplicate entry, downstream consumers behave inconsistently: resolveLocales() in src/app/sitemap.ts defensively filters out the default locale, but LanguageSwitcher.tsx builds its list as [getDefaultLocale(), ...getPrefixedLocales()] without filtering — producing a duplicate entry (and a duplicate React key) in the language dropdown.

Centralizing the exclusion/dedupe here removes the need for every consumer to re-implement the same guard.

🔧 Proposed fix
 export function getPrefixedLocales(): string[] {
-  return (process.env.NEXT_PUBLIC_PREFIXED_LOCALES || "en")
-    .split(",")
-    .map((l) => l.trim().toLowerCase())
-    .filter(Boolean);
+  const defaultLocale = getDefaultLocale();
+  const locales = (process.env.NEXT_PUBLIC_PREFIXED_LOCALES || "en")
+    .split(",")
+    .map((l) => l.trim().toLowerCase())
+    .filter(Boolean);
+  return [...new Set(locales)].filter((l) => l !== defaultLocale);
 }
📝 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
/**
* Locale codes shown with a URL prefix (e.g. 'en' -> /en/...). Kept in sync
* with the middleware's `supportedLocales` default (see proxy.ts) — this is
* the single source of truth for "which locales does this store serve",
* consumed by both the middleware and the sitemap generator.
*/
export function getPrefixedLocales(): string[] {
return (process.env.NEXT_PUBLIC_PREFIXED_LOCALES || "en")
.split(",")
.map((l) => l.trim().toLowerCase())
.filter(Boolean);
}
/**
* Locale codes shown with a URL prefix (e.g. 'en' -> /en/...). Kept in sync
* with the middleware's `supportedLocales` default (see proxy.ts) — this is
* the single source of truth for "which locales does this store serve",
* consumed by both the middleware and the sitemap generator.
*/
export function getPrefixedLocales(): string[] {
const defaultLocale = getDefaultLocale();
const locales = (process.env.NEXT_PUBLIC_PREFIXED_LOCALES || "en")
.split(",")
.map((l) => l.trim().toLowerCase())
.filter(Boolean);
return [...new Set(locales)].filter((l) => l !== defaultLocale);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/store.ts` around lines 69 - 80, `getPrefixedLocales()` currently
returns raw env values, which can include the default locale and duplicate
entries. Update `getPrefixedLocales` in `src/lib/store.ts` to normalize, dedupe,
and exclude `getDefaultLocale()` so it remains the single source of truth for
consumers like `LanguageSwitcher` and `resolveLocales`. Keep the filtering
centralized in this helper rather than re-adding guards in each caller.

pawelekbyra and others added 5 commits July 9, 2026 21:55
…ility

Next.js 15+ cacheComponents feature is incompatible with the dynamic
export. Use revalidate = 0 instead, which achieves the same effect
(disables caching entirely) but is compatible with all config options.

revalidate = 0 means:
- Never cache the page
- Always fetch fresh data from the API
- No need for static generation during build time
- Avoids self-signed certificate validation issues

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
cacheComponents (React 19 experimental feature) is incompatible with
ANY route segment config exports (dynamic, revalidate, etc.). Since we
need dynamic = 'force-dynamic' to avoid pre-rendering API calls during
build time, we must disable this optimization.

cacheComponents was intended for automatic component caching but conflicts
with explicit route configuration. Disabling it allows our routes to skip
pre-rendering and fetch data at runtime, avoiding self-signed certificate
validation errors.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
generateStaticParams() was pre-building the homepage for every locale
during build time, which triggered API data fetching and self-signed
cert validation errors. Since we have dynamic = 'force-dynamic', all
rendering happens at request time anyway, so this pre-generation is
unnecessary and blocks the build.

Keep generateMetadata but wrap in try/catch to gracefully handle API
errors during metadata generation (some tools still call it at build time).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
… cacheComponents

The 'use cache: remote' and cacheLife() directives require cacheComponents
to be enabled in next.config, but we disabled it to allow dynamic = 'force-dynamic'.
These are now mutually exclusive.

Since we're using dynamic = 'force-dynamic' to skip pre-rendering entirely,
we don't need remote caching directives anyway. Keep cacheTag() for tagged
revalidation via webhooks/manual purging.

Removed from:
- src/lib/data/products.ts (cachedListProducts, cachedGetProduct, cachedGetProductFilters)
- src/lib/data/store.ts (cachedGetStore)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
… data files

Removed from:
- src/lib/data/categories.ts (3 occurrences)
- src/lib/data/countries.ts (1 occurrence)
- src/lib/data/markets.ts (3 occurrences)

These directives require cacheComponents to be enabled, which conflicts
with dynamic = 'force-dynamic'. Now all data files use cacheTag() only
for tagged revalidation via webhooks.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

pawelekbyra and others added 2 commits July 9, 2026 22:24
…ire src tree

Found additional 'use cache: remote' directives in components like
CategoryBanner.tsx that weren't in src/lib/data. Used find + sed to
remove ALL occurrences from entire src/ tree.

This prevents the Turbopack error: 'To use use cache: remote, please
enable the feature flag cacheComponents in your Next.js config' while
cacheComponents is disabled to allow dynamic = 'force-dynamic'.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The earlier bulk removal of 'use cache: remote' and cacheLife left
cacheTag() calls in place across 6 files, and in one case (products.ts,
store.ts, markets.ts, countries.ts, categories.ts) the sed pass for
cacheLife also deleted the shared import line
'import { cacheLife, cacheTag } from "next/cache"' entirely, since it
matched on the substring cacheLife. That broke the build with
'Cannot find name cacheTag'.

cacheTag() only has meaning inside a 'use cache' scope (which requires
cacheComponents, now disabled) — re-adding just the import would let
TypeScript pass but throw at runtime ('used outside a cache scope').
So instead of restoring the import, removed the now-meaningless
cacheTag() calls entirely from:
- src/app/[locale]/(storefront)/c/[...permalink]/CategoryBanner.tsx
- src/lib/data/{categories,countries,markets,products,store}.ts

The revalidateTag() calls in src/lib/webhooks/handlers.ts that targeted
these tag names become no-ops for now (nothing to invalidate without a
cache), which is safe and matches the already-disabled caching.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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