Fix/force dynamic storefront - #170
Conversation
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>
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>
|
@pawelekbyra is attempting to deploy a commit to the Spree Commerce Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reached
Next review available in: 37 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
WalkthroughThis PR migrates routing from ChangesStorefront localization and platform update
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
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
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (5)
CLAUDE.md (1)
42-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd the new API route groups to the tree.
The structure diagram now stops at
api/webhooks/, but this stack also addsapi/cron/andapi/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 winUse
satisfiesinstead of a type annotation per coding guidelines.As per coding guidelines, object literals should use
satisfiesfor type checking. This preserves the literal type while still validating conformance toRecord<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 winAdd explicit return type to
handleProductChanged.Per coding guidelines, all TypeScript functions should define explicit return types.
handleProductChangedis 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 winExtract shared empty
PaginatedResponse<Product>helper to avoid duplication.
emptyCategoryProductsResponse()here is identical toemptyProductListResponse()insrc/lib/data/products.ts(lines 13–29) — samedata: [], samemetafields, sameas unknown ascast. 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.tsandproducts.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 | 🔵 TrivialConsider 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (78)
.deploy-trigger.env.exampleAGENTS.mdCLAUDE.mdREADME.mddocs/README.mddocs/deployment-vercel.mddocs/kierunek-frontu.mddocs/technical-debt.mdmessages/de.jsonmessages/en.jsonmessages/es.jsonmessages/fr.jsonmessages/pl.jsonnext.config.tssrc/app/[country]/[locale]/(storefront)/page.tsxsrc/app/[locale]/(checkout)/checkout/[id]/CheckoutPageContent.tsxsrc/app/[locale]/(checkout)/checkout/[id]/CheckoutSidebar.tsxsrc/app/[locale]/(checkout)/checkout/[id]/page.tsxsrc/app/[locale]/(checkout)/confirm-payment/[id]/page.tsxsrc/app/[locale]/(checkout)/confirm-payment/__tests__/page.test.tsxsrc/app/[locale]/(checkout)/layout.tsxsrc/app/[locale]/(checkout)/order-placed/[id]/page.tsxsrc/app/[locale]/(storefront)/account/addresses/page.tsxsrc/app/[locale]/(storefront)/account/credit-cards/page.tsxsrc/app/[locale]/(storefront)/account/forgot-password/page.tsxsrc/app/[locale]/(storefront)/account/gift-cards/page.tsxsrc/app/[locale]/(storefront)/account/layout.tsxsrc/app/[locale]/(storefront)/account/orders/[id]/page.tsxsrc/app/[locale]/(storefront)/account/orders/page.tsxsrc/app/[locale]/(storefront)/account/page.tsxsrc/app/[locale]/(storefront)/account/profile/page.tsxsrc/app/[locale]/(storefront)/account/register/page.tsxsrc/app/[locale]/(storefront)/account/reset-password/page.tsxsrc/app/[locale]/(storefront)/c/[...permalink]/CategoryBanner.tsxsrc/app/[locale]/(storefront)/c/[...permalink]/page.tsxsrc/app/[locale]/(storefront)/cart/page.tsxsrc/app/[locale]/(storefront)/layout.tsxsrc/app/[locale]/(storefront)/page.tsxsrc/app/[locale]/(storefront)/policies/[slug]/page.tsxsrc/app/[locale]/(storefront)/products/[slug]/ProductDetails.tsxsrc/app/[locale]/(storefront)/products/[slug]/page.tsxsrc/app/[locale]/(storefront)/products/page.tsxsrc/app/[locale]/layout.tsxsrc/app/api/cron/sync-eur-prices/route.tssrc/app/api/revalidate/route.tssrc/app/api/webhooks/spree/route.tssrc/app/robots.tssrc/app/sitemap.tssrc/components/home/FeaturedProductsSection.tsxsrc/components/home/HeroSection.tsxsrc/components/layout/CountrySwitcher.tsxsrc/components/layout/Footer.tsxsrc/components/layout/Header.tsxsrc/components/layout/LanguageSwitcher.tsxsrc/components/layout/MobileMenu.tsxsrc/components/products/FeaturedProducts.tsxsrc/components/products/MediaGallery.tsxsrc/components/products/ProductListing.tsxsrc/hooks/useCountrySwitch.tssrc/lib/data/categories.tssrc/lib/data/markets.tssrc/lib/data/products.tssrc/lib/data/store.tssrc/lib/metadata/category.tssrc/lib/metadata/home.tssrc/lib/metadata/product.tssrc/lib/metadata/products.tssrc/lib/seo.tssrc/lib/spree/config.tssrc/lib/spree/middleware.tssrc/lib/store.tssrc/lib/utils/language-names.tssrc/lib/utils/path.tssrc/lib/webhooks/__tests__/handlers.test.tssrc/lib/webhooks/handlers.tssrc/proxy.tsvercel.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
| 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`. | ||
|
|
There was a problem hiding this comment.
📐 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.
| 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.
| ## 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. |
There was a problem hiding this comment.
📐 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.
| ```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 |
There was a problem hiding this comment.
📐 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.
| ```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" }) { |
There was a problem hiding this comment.
🎯 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.tsxRepository: 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.tsxRepository: 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.tsxRepository: 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" }); |
There was a problem hiding this comment.
🩺 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 -5Repository: 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
fiRepository: 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)
JSRepository: 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.
| const locales = [ | ||
| getDefaultLocale() as Locale, | ||
| ...getPrefixedLocales().map((l) => l as Locale), | ||
| ]; |
There was a problem hiding this comment.
🎯 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.
| 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.
| async function cachedGetStore(): Promise<StoreInfo> { | ||
| "use cache: remote"; | ||
| cacheLife("hours"); | ||
| cacheTag("store"); | ||
| return getClient().request<StoreInfo>("GET", "/store"); | ||
| } |
There was a problem hiding this comment.
🩺 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 -20Repository: 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:
- 1: https://nextjs.org/docs/app/api-reference/directives/use-cache-remote
- 2: vercel/next.js@27a3fa7
- 3: https://devcheolu.com/en/posts/5yBz0d7cPurga9CXmUwI
🏁 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}' || trueRepository: 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
| 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.", |
There was a problem hiding this comment.
🎯 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' || trueRepository: 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")
PYRepository: 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")
PYRepository: 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.
| const defaultCountry = config.defaultCountry ?? "pl"; | ||
| const defaultLocale = config.defaultLocale ?? "pl"; | ||
| const supportedLocales = config.supportedLocales ?? ["en"]; |
There was a problem hiding this comment.
🎯 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.
| 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.
| /** | ||
| * 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); | ||
| } |
There was a problem hiding this comment.
🎯 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.
| /** | |
| * 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.
…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>
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
…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>
Summary by CodeRabbit
New Features
Bug Fixes