myowjaYOY/commerce
April 19, 2026
Code Health Assessment
commerce
Generated by DocAgent — automated codebase documentation analysis. Based on analysis of 5 screens. Subject matter expert review is recommended before distribution.
April 20, 2026
Code Health Assessment
Application: commerce Document Title: Code Health Assessment Date: April 2026
Generated by DocAgent — automated codebase documentation analysis. Subject matter expert review is recommended before distribution.
This assessment covers 5 screens of the commerce application. Overall, the codebase is rated Moderate Concern. The application demonstrates a strong architectural foundation: it is built on Next.js 15 App Router with React Server Components used consistently and correctly, TypeScript strict mode is enabled throughout, and the Shopify integration is cleanly abstracted behind a lib/shopify service layer. The deliberate use of streaming SSR via Suspense boundaries on the Product Detail screen and the server-side-only handling of API credentials reflect sound engineering judgment.
The three highest-risk findings are: [CH-003] Unguarded featuredImage Property Access in JSON-LD — a null dereference that will throw a runtime error for any Shopify product without a featured image, causing a 500 response on the primary conversion page; [CH-002] dangerouslySetInnerHTML JSON-LD Without </script> Escape Hardening — a known XSS vector on the Product Detail page where JSON.stringify does not neutralize </script> sequences embedded in Shopify-sourced data; and [CH-001] Duplicate Shopify API Calls Across generateMetadata and Page Components — a cross-cutting pattern affecting three screens where the same data is fetched twice per request, doubling Shopify API load and increasing latency when fetch deduplication is not guaranteed.
The assessment is based entirely on per-screen technical documentation. No static analysis tools, runtime profiling, test coverage measurement, or direct source code access was used. Findings marked "(based on documentation — verify with static analysis)" require tooling confirmation before acting. See the Finding Inventory for the complete catalog of findings by severity.
Quality Model
Findings are classified against ISO/IEC 25010:2023 quality characteristics: Maintainability, Reliability, Security, Performance Efficiency, Functional Suitability, Usability, Compatibility, and Portability.
Debt Classification
Technical debt is classified using the SQALE method: Design Debt, Code Debt, Test Debt, Doc Debt, and Dependency Debt.
Severity Model
Aligned with SonarQube's quality model:
| Severity | Definition | Impact | SLA |
|---|---|---|---|
| Blocker | Defect that will cause production failure or data loss | System-breaking | Fix immediately |
| Critical | Severe code quality issue with high probability of causing bugs | Major risk | Fix within current sprint |
| Major | Significant quality issue that degrades maintainability or reliability | Moderate risk | Fix within 30 days |
| Minor | Code quality issue with limited impact | Low risk | Fix within 90 days |
| Info | Best practice recommendation or improvement suggestion | No immediate risk | Consider for future work |
Scope
This assessment covers 5 screens of the commerce application:
Home (/)
Item Detail (/[page])
Product Detail (/product/[handle])
Search (/search)
Search Detail (/search/[collection])
Screens, features, and components not included in the assessed documentation are outside the scope of this document. Specifically, the lib/shopify service layer, shared components (ThreeItemGrid, Carousel, Gallery, ProductDescription, ProductGridItems, Grid, Footer, Prose, GridTileImage, CartProvider, Navbar, WelcomeToast), cart/checkout flows, and account management screens are not assessed here.
Limitations
All findings are derived from per-screen technical documentation, not from automated static analysis, runtime profiling, or direct source code inspection.
Cyclomatic complexity, lines of code, test coverage percentages, and bundle size metrics cannot be measured from documentation alone.
Git history (churn rate, contributor patterns, commit frequency) is unavailable.
Findings about shared components and the lib/shopify service layer are inferred from how they are described in screen documentation and are placed in the Needs Verification section.
| Quality Dimension | Score | Rating | Key Finding | Findings Count |
|---|---|---|---|---|
| Maintainability | 4/5 | Good | Duplicate API call pattern across three screens is the primary debt item | 2 |
| Reliability | 3/5 | Fair | Unguarded null dereference on Product Detail will cause 500 errors in production | 4 |
| Security | 3/5 | Fair | JSON-LD </script> injection vector and unsafe searchParams type cast require hardening | 2 |
| Performance Efficiency | 4/5 | Good | Streaming SSR and RSC usage are strong; pagination absence is a scalability gap | 2 |
| Functional Suitability | 3/5 | Fair | Server locale date formatting produces incorrect output for international users | 2 |
| Usability | 3/5 | Fair | Missing loading skeletons and absent ARIA live regions degrade perceived performance and accessibility | 3 |
| Compatibility | 4/5 | Good | Canary Next.js version introduces upgrade risk; experimental flags require monitoring | 1 |
| Portability | 4/5 | Good | Clean Shopify abstraction; image domain allowlist is appropriately scoped | 1 |
[Diagram: Component dependency graph showing the five assessed page components, their shared child components (ThreeItemGrid, Carousel, Gallery, ProductDescription, ProductGridItems, Grid, Footer, Prose), and the lib/shopify service layer — illustrating the layered architecture and the points where duplicate API calls occur.]
Overview
The codebase follows the Next.js App Router convention of thin page components that delegate data fetching and rendering to child components. TypeScript strict mode (strict: true, noUncheckedIndexedAccess: true) is enabled, which significantly reduces the surface area for type-related bugs. The primary maintainability concern is a cross-cutting pattern of duplicate API calls that affects three screens and would be resolved by a single shared utility.
CH-001: Duplicate Shopify API Calls Across generateMetadata and Page Components
Severity: Major Quality Sub-Characteristic: Reusability Screen(s): Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection]) Component(s): generateMetadata and page component functions in app/[page]/page.tsx, app/product/[handle]/page.tsx, app/search/[collection]/page.tsx
Description: On three screens, the same Shopify API fetch function is called independently in both generateMetadata and the page component body for the same request. This pattern doubles the number of outbound Shopify API calls per page render unless Next.js's fetch deduplication layer coalesces them. Deduplication is not guaranteed — it depends on whether lib/shopify uses the native fetch API with identical URL and options, and whether cache: 'no-store' is set anywhere in the call chain. The team has documented this as a known issue on two of the three affected screens.
Evidence:
Item Detail (/[page]): "Both generateMetadata and the Page component independently call getPage(params.page)... this results in two identical Shopify API requests per page render" (§17)
Product Detail (/product/[handle]): "getProduct(params.handle) is called independently in both generateMetadata and ProductPage for the same handle on the same request" (§17)
Search Detail (/search/[collection]): getCollection(params.collection) is called in generateMetadata and the collection slug is also used in getCollectionProducts in the page body (§5)
Risk: Each affected page render may issue two Shopify Storefront API calls instead of one. Shopify's Storefront API enforces rate limits; under load, doubling the call rate increases the probability of hitting rate limits, which would cause 429 errors and degrade page availability. Additionally, each extra round-trip adds latency to the server render.
Recommendation:
// lib/shopify.ts (or a per-route cache wrapper)
import { cache } from 'react';
export const getProduct = cache(async (handle: string) => {
// existing implementation
});
Apply the same pattern to getPage and getCollection.
After applying React.cache(), verify with Shopify API request logs that only one call per handle is made per page render.
Effort: Low (< 1 hour per function; 3 functions total)
SQALE Characteristic: Maintainability
Source Evidence: Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection])
CH-010: Unsafe searchParams Type Cast Suppresses Runtime Safety
Severity: Major Quality Sub-Characteristic: Analysability Screen(s): Search (/search), Search Detail (/search/[collection]) Component(s): app/search/page.tsx, app/search/[collection]/page.tsx
Description: Both search screens cast searchParams using as { [key: string]: string }, which suppresses TypeScript's awareness that query parameter values can be string | string[] | undefined. This means the sort and q values passed downstream to getProducts and getCollectionProducts may be string[] or undefined at runtime without any type error being raised. The team has documented this as a known issue on both screens.
Evidence:
Search (/search) §17: "The line const { sort, q: searchValue } = searchParams as { [key: string]: string } uses a broad type cast that suppresses TypeScript's awareness of undefined and string[] values."
Search Detail (/search/[collection]) §17: "The line const { sort } = searchParams as { [key: string]: string } performs an unchecked cast."
Risk: A URL with duplicate parameters (e.g., ?sort=price-asc&sort=trending) passes a string[] to getProducts or getCollectionProducts. If those functions do not guard against array inputs, the behavior is undefined — it may silently fall back to defaultSort, pass an array as a GraphQL variable (causing an API error), or produce unexpected results. This is a latent bug that is difficult to detect in testing because it requires a crafted URL.
Recommendation:
// Safe extraction with runtime type guard
const rawSort = searchParams?.sort;
const sort = typeof rawSort === 'string' ? rawSort : undefined;
const rawQ = searchParams?.q;
const searchValue = typeof rawQ === 'string' ? rawQ : undefined;
Apply the same pattern to the collection parameter on the Search Detail screen.
Consider a shared utility function getSingleParam(params: unknown, key: string): string | undefined to centralize this pattern across all search-related pages.
Effort: Low (< 1 hour per screen; 2 screens)
SQALE Characteristic: Reliability
Source Evidence: Search (/search), Search Detail (/search/[collection])
Overview
The most significant reliability risk is a confirmed null dereference on the primary conversion page (Product Detail). Additionally, the absence of error boundaries at the page level means Shopify API failures propagate as unhandled server errors across all five screens. The team has documented several of these gaps in §17 of the affected screens, which reflects good awareness of the debt.
CH-002: Unguarded featuredImage Property Access in JSON-LD
Severity: Critical Quality Sub-Characteristic: Fault Tolerance Screen(s): Product Detail (/product/[handle]) Component(s): productJsonLd construction in app/product/[handle]/page.tsx
Description: The productJsonLd object accesses product.featuredImage.url directly without a null or undefined guard. The generateMetadata function on the same screen correctly handles this case with optional chaining (product.featuredImage || {}), but the JSON-LD block does not apply the same defensive pattern. Any Shopify product that has no featured image configured will cause a runtime TypeError when the page renders, resulting in a 500 error response for that product URL.
Evidence: Product Detail (/product/[handle]) §10: "The JSON-LD block accesses product.featuredImage.url directly without a null guard — if featuredImage is undefined, this would throw a runtime error." Also documented in §17: "Potential null dereference on featuredImage."
Risk: Any product in the Shopify catalog without a featured image will return a 500 error instead of a product page. This is a production-breaking defect for affected products — shoppers cannot view or purchase those items, and the error will appear in server logs without a clear user-facing message.
Recommendation:
// Before (unsafe)
image: product.featuredImage.url,
// After (safe — consistent with generateMetadata pattern)
...(product.featuredImage?.url && { image: product.featuredImage.url }),
Add a unit test asserting that productJsonLd is constructed without error when product.featuredImage is undefined.
Audit lib/shopify's getProduct return type to confirm whether featuredImage is typed as optional — if it is typed as required but can be null at runtime, the type definition is incorrect and should be fixed.
Effort: Low (< 1 hour)
SQALE Characteristic: Reliability
Source Evidence: Product Detail (/product/[handle])
CH-004: No Error Boundaries at Page Level — Shopify API Failures Produce Unhandled Server Errors
Severity: Major Quality Sub-Characteristic: Fault Tolerance Screen(s): Home (/), Item Detail (/[page]), Product Detail (/product/[handle]), Search (/search), Search Detail (/search/[collection]) Component(s): All five assessed page components; RelatedProducts server component
Description: None of the five assessed page components wrap their data-fetching calls in try/catch blocks, and none have co-located error.tsx files documented. When getPage, getProduct, getProducts, getCollection, or getCollectionProducts throw (network failure, Shopify rate limit, malformed response), the error propagates as an unhandled server exception. The behavior depends on whether a parent layout defines an error.tsx boundary — if none exists, Next.js renders a generic error page with no user-friendly messaging. The RelatedProducts component on the Product Detail screen is specifically called out as having no error boundary.
Evidence:
Item Detail §10: "A Shopify API network failure — Unhandled at this component level; propagates as an unhandled server error."
Product Detail §10: "No error boundaries: Neither RelatedProducts nor the Gallery/ProductDescription Suspense wrappers include React Error Boundaries."
Product Detail §17: "A failure in getProductRecommendations (network error, Shopify API error) has no error boundary."
Search §10: "There is no try/catch block in the page component wrapping the getProducts call."
Search Detail §10: "Shopify API failure in getCollectionProducts — Error propagates to Next.js error boundary (defined at layout level, not in this file)."
Risk: A transient Shopify API outage or rate-limit event causes all affected pages to return 500 errors simultaneously, with no graceful degradation. Users see a generic error page rather than a helpful message. For the Product Detail screen, a failure in RelatedProducts could take down the entire product page even though the core product content loaded successfully.
Recommendation:
Add a co-located error.tsx file at the app/ level (or per-route where appropriate) that renders a user-friendly error message and a retry option.
Wrap the RelatedProducts component in a React Error Boundary so that a failure in related product recommendations does not affect the main product card:
// app/product/[handle]/page.tsx
<Suspense fallback={<RelatedProductsSkeleton />}>
<ErrorBoundary fallback={null}>
<RelatedProducts id={product.id} />
</ErrorBoundary>
</Suspense>
[Not documented — WHO: tech lead; WHAT: Does a root-level app/error.tsx or app/global-error.tsx file exist in the repository?; WHERE: Insert answer in CH-004 above — if a root error boundary exists, severity of this finding should be reduced to Minor (defense-in-depth gap only).]
Effort: Medium (1–4 hours)
SQALE Characteristic: Reliability
Source Evidence: Item Detail (/[page]), Product Detail (/product/[handle]), Search (/search), Search Detail (/search/[collection])
CH-005: Invalid Date Rendered to Users When page.updatedAt Is Malformed
Severity: Minor Quality Sub-Characteristic: Fault Tolerance Screen(s): Item Detail (/[page]) Component(s): Date formatting inline expression in app/[page]/page.tsx
Description: The last-updated date is formatted inline using new Intl.DateTimeFormat(undefined, {...}).format(new Date(page.updatedAt)). If page.updatedAt is an empty string, null, or a non-ISO date string, new Date(...) produces an Invalid Date object, and Intl.DateTimeFormat.format() outputs the literal string "Invalid Date" to the rendered page. This is a user-visible data quality failure.
Evidence: Item Detail §10: "page.updatedAt is malformed → new Date(page.updatedAt) produces an Invalid Date; Intl.DateTimeFormat.format() would output 'Invalid Date' as a string."
Risk: Shoppers see "Invalid Date" in the last-updated line of informational pages (Privacy Policy, About Us, etc.), which undermines trust in the storefront's professionalism. While Shopify typically returns well-formed ISO 8601 timestamps, defensive handling is warranted for any externally sourced date value.
Recommendation:
const updatedDate = new Date(page.updatedAt);
const formattedDate = isNaN(updatedDate.getTime())
? null
: new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(updatedDate);
// In JSX:
{formattedDate && (
<p className="text-sm italic">
{`This document was last updated on ${formattedDate}`}
</p>
)}
Effort: Low (< 1 hour)
SQALE Characteristic: Reliability
Source Evidence: Item Detail (/[page])
CH-006: No Test Suite Present Across All Assessed Screens
Severity: Major Quality Sub-Characteristic: Testability Screen(s): Home (/), Item Detail (/[page]), Product Detail (/product/[handle]), Search (/search), Search Detail (/search/[collection]) Component(s): All assessed page components
Description: No co-located test files are present for any of the five assessed screens. The package.json test script runs only prettier:check (a formatting check), confirming that no test runner (Jest, Vitest, Playwright, Cypress) is configured in the project manifest. The documentation for each screen includes a recommended testing strategy, indicating the team is aware of the gap.
Evidence:
All five screens §16: "No co-located tests found in the collected files."
package.json scripts.test: "pnpm prettier:check" — no test runner invocation present.
No jest, vitest, @playwright/test, or cypress packages appear in package.json dependencies or devDependencies.
Risk: Without automated tests, regressions in critical paths (product not found → 404, add-to-cart flow, search result rendering) are only caught manually or in production. The productJsonLd null dereference (CH-002) and the Invalid Date rendering (CH-005) are examples of defects that unit tests would have caught before deployment.
Recommendation:
Add a test runner to the project. Given the Next.js App Router and React Server Component architecture, Vitest with @testing-library/react is recommended for unit and integration tests; Playwright is recommended for E2E tests.
Prioritize tests for the highest-risk paths first:
productJsonLd construction with featuredImage: undefined (covers CH-002)
generateMetadata not-found handling for all three dynamic routes
Effort: Very High (16+ hours — initial test infrastructure setup + first test suite)
SQALE Characteristic: Testability
Source Evidence: Home (/), Item Detail (/[page]), Product Detail (/product/[handle]), Search (/search), Search Detail (/search/[collection])
Overview
The application's security posture is reasonable for a public-facing read-only storefront: all Shopify API credentials are server-side only, no authentication tokens are exposed to the client, and the GraphQL parameterization in lib/shopify prevents injection attacks. Two specific hardening gaps require attention: an XSS vector in the JSON-LD script tag and an unsafe type cast that could pass unexpected values to the Shopify API.
CH-003: dangerouslySetInnerHTML JSON-LD Without </script> Escape Hardening
Severity: Critical Quality Sub-Characteristic: Security (Confidentiality / Integrity) Screen(s): Product Detail (/product/[handle]) Component(s): JSON-LD <script> tag in app/product/[handle]/page.tsx
Description: The structured data block uses dangerouslySetInnerHTML={{ __html: JSON.stringify(productJsonLd) }} to inject JSON-LD into a <script> tag. JSON.stringify does not escape the </script> character sequence. If any Shopify-sourced string field (product title, description, or SEO fields) contains the literal text </script>, the browser's HTML parser will interpret it as closing the script tag, potentially allowing injected HTML or script content to execute. This is a documented XSS vector for inline JSON-LD. The team has documented this in §17.
Evidence: Product Detail §14: "if Shopify-sourced data contained a </script> sequence, it could theoretically break the script tag. A more robust approach would use a JSON serializer that escapes <, >, and & characters." Also §17: "JSON.stringify does not escape </script> sequences, which is a known XSS vector for inline JSON-LD."
Risk: A Shopify merchant (or an attacker with access to Shopify admin) who sets a product title or description containing </script><script>alert(1)</script> could inject arbitrary JavaScript into every visitor's browser on that product page. While this requires Shopify admin access, it is a meaningful risk in multi-merchant or compromised-admin scenarios, and it violates defense-in-depth principles.
Recommendation:
function safeJsonLd(data: object): string {
return JSON.stringify(data)
.replace(/</g, '\u003c')
.replace(/>/g, '\u003e')
.replace(/&/g, '\u0026');
}
// Usage:
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: safeJsonLd(productJsonLd) }}
/>
This is a one-line utility function; extract it to lib/utils.ts for reuse if JSON-LD is added to other pages.
Add a unit test asserting that a product with </script> in its title produces escaped output.
Effort: Low (< 1 hour)
SQALE Characteristic: Security
Source Evidence: Product Detail (/product/[handle])
CH-007: dangerouslySetInnerHTML in Prose Component — XSS Dependency on Shopify Output Sanitization
Severity: Major Quality Sub-Characteristic: Security (Integrity) Screen(s): Item Detail (/[page]) Component(s): Prose component (components/prose), used in app/[page]/page.tsx
Description: The Prose component renders page.body — a raw HTML string from Shopify's CMS — using dangerouslySetInnerHTML (implied by the html prop pattern). The security of this rendering depends entirely on two factors: (1) whether Shopify sanitizes its page body output before returning it via the Storefront API, and (2) whether the Prose component applies any additional sanitization. Neither factor is confirmed in the provided documentation. If a Shopify page body contains a <script> tag or an event handler attribute (e.g., <img onerror="...">), it would execute in the visitor's browser.
Evidence: Item Detail §3: "The component applies typographic prose styling to the injected HTML. It receives... an html prop containing the raw HTML string. This component uses dangerouslySetInnerHTML internally (implied by the html prop pattern)." Item Detail §14: "if the Prose component uses dangerouslySetInnerHTML without sanitization, any XSS payloads stored in Shopify page content would be rendered. The security posture depends entirely on the Prose component's implementation and Shopify's own output sanitization."
Risk: If Shopify's Storefront API returns unsanitized HTML and the Prose component does not sanitize it, a merchant (or attacker with Shopify admin access) can inject arbitrary JavaScript into informational pages (Privacy Policy, About Us, etc.), affecting all visitors.
Recommendation:
Verify whether Shopify's Storefront API sanitizes page.body HTML output. Review Shopify's API documentation for the Page.body field's sanitization guarantees.
Inspect the Prose component source to confirm whether it applies HTML sanitization (e.g., using DOMPurify or a similar library) before passing content to dangerouslySetInnerHTML.
If neither Shopify nor Prose sanitizes the content, add client-side sanitization using isomorphic-dompurify or sanitize-html:
import DOMPurify from 'isomorphic-dompurify';
// In Prose component:
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />
Effort: Medium (1–4 hours — dependency on Prose component review)
SQALE Characteristic: Security
Source Evidence: Item Detail (/[page])
Overview
The application makes excellent use of Next.js 15's performance capabilities: React Server Components eliminate client-side JavaScript for all five assessed pages, streaming SSR via Suspense is used on the Product Detail screen, and prefetch={true} on related product links reduces navigation latency. The primary performance concern is the absence of pagination on search and collection pages, which is a scalability risk for large catalogs.
CH-008: No Pagination on Search and Collection Product Listings
Severity: Major Quality Sub-Characteristic: Time Behaviour Screen(s): Search (/search), Search Detail (/search/[collection]) Component(s): getProducts and getCollectionProducts calls in app/search/page.tsx, app/search/[collection]/page.tsx
Description: Both search screens fetch all matching products in a single API call with no pagination, infinite scroll, or cursor-based loading. The maximum number of products returned is governed by lib/shopify's implementation (not assessed here). For stores with large catalogs or broad search terms, this pattern can produce slow server renders, large HTML payloads, and high memory usage. The team has documented this as a known issue on both screens.
Evidence:
Search §12: "There is no pagination, infinite scroll, or virtual list implementation. For stores with large product catalogs, this could result in slow renders or large payloads."
Search Detail §12: "All products in the collection are fetched and rendered in a single request. For collections with very large product counts, this could become a performance concern."
Search §17: "No pagination: For stores with large catalogs, the lack of pagination could be a scalability concern."
Search Detail §17: "No pagination: All products in a collection are fetched in a single call."
Risk: As the product catalog grows, search and collection pages will become progressively slower. A collection with hundreds of products will produce a large server render time and a large HTML payload, degrading Core Web Vitals (LCP, FID) and potentially causing timeouts on the Shopify API call.
Recommendation:
Implement cursor-based pagination using Shopify's Storefront API pageInfo { hasNextPage, endCursor } pattern.
For the initial implementation, a "Load More" button (appending to the current list) is simpler than full infinite scroll and avoids the accessibility concerns of infinite scroll.
Alternatively, implement URL-based page navigation (?page=2) for deep-linkable paginated results.
Set an explicit product limit (e.g., 24 or 48 per page) in getProducts and getCollectionProducts as an immediate mitigation even before full pagination UI is built.
[Not documented — WHO: tech lead or Shopify integration owner; WHAT: What is the current first: limit set in the getProducts and getCollectionProducts GraphQL queries in lib/shopify?; WHERE: Insert in CH-008 above — if the limit is already set to a reasonable value (e.g., ≤ 50), severity of this finding may be reduced to Minor.]
Effort: High (4–16 hours)
SQALE Characteristic: Efficiency
Source Evidence: Search (/search), Search Detail (/search/[collection])
CH-009: Experimental Next.js Features in Production Configuration
Severity: Minor Quality Sub-Characteristic: Operability (Compatibility) Screen(s): All screens Component(s): next.config.ts
Description: The next.config.ts enables three experimental Next.js features: ppr (Partial Prerendering), inlineCss, and useCache. Additionally, the project uses next@15.6.0-canary.60 — a canary (pre-release) build. Experimental features and canary builds are subject to breaking changes between releases without semantic versioning guarantees. The useCache directive in particular is a new caching API that is not yet stable.
Evidence: next.config.ts: experimental: { ppr: true, inlineCss: true, useCache: true }. package.json: "next": "15.6.0-canary.60".
Risk: A Next.js canary update could introduce breaking changes to any of the three experimental APIs, causing build failures or runtime regressions without a major version bump. The useCache API is particularly new and its behavior may change. This is a low-probability but high-impact risk for a production storefront.
Recommendation:
Track the Next.js changelog for stabilization of ppr, inlineCss, and useCache. Pin the canary version explicitly (already done via 15.6.0-canary.60) and establish a deliberate upgrade process rather than using a range specifier.
When Next.js 15 stable releases these features, migrate from experimental.* flags to the stable API.
Document the rationale for using canary in the project README so future contributors understand the intentional choice.
Effort: Low (< 1 hour for documentation; Medium for migration when stable APIs land)
SQALE Characteristic: Portability
Source Evidence: next.config.ts, package.json
Overview
The core e-commerce flows (product browsing, search, collection filtering, product detail) are functionally complete within the assessed screens. Two functional correctness issues are present: server-locale date formatting that produces incorrect output for international users, and the soft-hide product mechanism that may not meet merchant expectations for truly hidden products.
CH-011: Server Locale Used for Date Formatting — Incorrect Output for International Users
Severity: Major Quality Sub-Characteristic: Functional Correctness Screen(s): Item Detail (/[page]) Component(s): Inline date formatting in app/[page]/page.tsx
Description: The last-updated date on informational pages is formatted using new Intl.DateTimeFormat(undefined, ...). When undefined is passed as the locale on a server, the formatter uses the server's runtime locale, not the visitor's browser locale. This means all users — regardless of their language or region — see the date formatted according to the server's locale setting. For example, a French user visiting an English-language server will see "April 15, 2026" rather than "15 avril 2026". The team has documented this as a known issue.
Evidence: Item Detail §8: "The Intl.DateTimeFormat is instantiated with undefined as the locale, which resolves to the runtime environment's default locale. On a server, this will be the server's locale rather than the user's browser locale — a subtle but important distinction." Also §17: "This means all users see the date formatted according to the server's locale setting, which may not match their expectations."
Risk: For international storefronts, all users see dates in the server's locale format. This is a functional correctness failure for non-English users and may cause confusion or distrust on legal/policy pages where date accuracy is important.
Recommendation:
For a server-rendered component, the user's locale is not directly available. Two approaches:
new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
.format(new Date(page.updatedAt))
Effort: Low (< 1 hour for option 1; Medium for option 2)
SQALE Characteristic: Maintainability
Source Evidence: Item Detail (/[page])
CH-012: Hidden Products Accessible via Direct URL — Soft-Hide Is Not Access Control
Severity: Info Quality Sub-Characteristic: Functional Completeness Screen(s): Product Detail (/product/[handle]) Component(s): HIDDEN_PRODUCT_TAG logic in app/product/[handle]/page.tsx
Description: Products tagged with HIDDEN_PRODUCT_TAG are excluded from search engine indexing (noindex, nofollow) but remain fully accessible via direct URL. This is a deliberate design decision documented in the screen. However, if merchants expect "hidden" products to be inaccessible to users (e.g., for staging new products or internal-only SKUs), the current implementation does not meet that expectation — it only prevents search engine discovery.
Evidence: Product Detail §2: "products tagged with the HIDDEN_PRODUCT_TAG constant are rendered normally for visitors but are marked noindex, nofollow in their metadata... This means hidden products are accessible via direct URL but will not appear in search engine results." §14: "This is a soft-hide mechanism, not an access control mechanism."
Risk: Merchants who use HIDDEN_PRODUCT_TAG expecting true access restriction will find that anyone with the direct URL can view and potentially purchase the product. This is a functional expectation mismatch rather than a code defect, but it should be clearly communicated.
Recommendation:
Document the soft-hide behavior explicitly in the project README or a merchant-facing guide so that the distinction between "hidden from search" and "access-restricted" is clear.
If true access restriction is required for any products, implement a middleware guard that checks for the tag and returns a 404 for direct URL access.
No code change is required if the current behavior is intentional and merchants are informed.
Effort: Low (< 1 hour for documentation; High if access restriction is required)
SQALE Characteristic: Maintainability
Source Evidence: Product Detail (/product/[handle])
Overview
The application provides a clean, server-rendered experience with good empty state handling on search and collection pages. The primary usability gaps are missing loading skeletons on several screens, absent ARIA live regions for dynamic content, and semantic HTML gaps that affect screen reader users.
CH-013: Missing <time> Element for Machine-Readable Dates
Severity: Minor Quality Sub-Characteristic: Accessibility (Usability) Screen(s): Item Detail (/[page]) Component(s): Last-updated paragraph in app/[page]/page.tsx
Description: The last-updated date on informational pages is rendered in a plain <p> tag with no <time> element or datetime attribute. The <time> element with a machine-readable datetime attribute improves semantic correctness, enables screen readers to announce dates in a locale-appropriate format, and allows browser extensions and search engines to parse the date accurately. The team has documented this gap in §17.
Evidence: Item Detail §15: "The Intl.DateTimeFormat date string is rendered in plain text without a <time> element and datetime attribute, which would improve machine-readability and screen reader context." Also §17: "No <time> element for the updated date."
Risk: Screen readers may announce the formatted date string verbatim rather than interpreting it as a date. Search engines cannot reliably parse the date for structured data purposes. This is a low-severity accessibility and semantic gap.
Recommendation:
<p className="text-sm italic">
{`This document was last updated on `}
<time dateTime={page.updatedAt}>
{new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(new Date(page.updatedAt))}
</time>
</p>
Effort: Low (< 1 hour)
SQALE Characteristic: Maintainability
Source Evidence: Item Detail (/[page])
CH-014: Missing Loading Skeletons on Search and Item Detail Screens
Severity: Minor Quality Sub-Characteristic: User Experience (Usability) Screen(s): Search (/search), Search Detail (/search/[collection]), Item Detail (/[page]) Component(s): Page-level loading state; absence of loading.tsx sibling files
Description: The Search, Search Detail, and Item Detail screens do not define loading.tsx sibling files (based on documentation evidence). Without a loading.tsx, Next.js App Router does not show a loading skeleton during server render. Users navigating to these pages during slow Shopify API responses will see a blank or stalled page rather than a skeleton that communicates progress. The Product Detail screen partially mitigates this with Suspense fallbacks on Gallery and ProductDescription, but the search screens have no equivalent.
Evidence:
Search §17: "there is no loading.tsx sibling file referenced, meaning users may see a blank or stalled page during slow Shopify API responses without a skeleton/loading UI."
Search Detail §12: "The page component itself defines no loading skeleton. This relies entirely on Next.js layout-level loading.tsx files or Suspense boundaries defined elsewhere in the app."
Home §10: "the page component does not define any loading.tsx or error.tsx co-located files (based on provided sources)."
Risk: During slow network conditions or Shopify API latency spikes, users see a blank page with no feedback, which increases perceived load time and bounce rate. This is a usability concern rather than a functional defect.
Recommendation:
Add loading.tsx files co-located with the affected page routes:
app/search/loading.tsx — skeleton with placeholder product grid cards
app/search/[collection]/loading.tsx — same skeleton
app/[page]/loading.tsx — skeleton with placeholder heading and text blocks
Use Tailwind's animate-pulse utility to create simple skeleton placeholders without additional dependencies.
Effort: Medium (1–4 hours for all three screens)
SQALE Characteristic: Usability
Source Evidence: Search (/search), Search Detail (/search/[collection]), Item Detail (/[page])
CH-015: Absent ARIA Live Regions for Search Result Announcements
Severity: Minor Quality Sub-Characteristic: Accessibility (Usability) Screen(s): Search (/search) Component(s): Results summary paragraph in app/search/page.tsx
Description: The Search screen renders a results count paragraph (e.g., "Showing 4 results for 'shoes'") but does not wrap it in an aria-live region. Because this is a server-rendered full-page navigation (not a client-side update), the page title change may serve as the primary announcement mechanism for screen readers. However, an explicit aria-live="polite" region on the results summary would provide a more reliable and explicit announcement of result counts to assistive technology users.
Evidence: Search §15: "There is no aria-live region to announce search result counts to screen reader users when results update. Since this is a server-rendered page (full navigation on search), the page title change may serve as the announcement mechanism, but an explicit live region would improve the experience."
Risk: Screen reader users may not receive a clear announcement of how many results were found after navigating to a search URL. This is a WCAG 2.1 Level AA concern (Success Criterion 4.1.3 — Status Messages).
Recommendation:
{searchValue && (
<p role="status" aria-live="polite">
{products.length === 0
? `There are no products that match "${searchValue}"`
: `Showing ${products.length} ${resultsText} for "${searchValue}"`}
</p>
)}
Effort: Low (< 1 hour)
SQALE Characteristic: Usability
Source Evidence: Search (/search)
Overview
The application targets modern browsers via es2015 TypeScript compilation target and uses standard Next.js image optimization with AVIF/WebP formats. The primary compatibility concern is the use of a canary Next.js build with experimental flags, which is addressed in CH-009.
No additional compatibility findings beyond CH-009 are evidenced in the documentation.
Overview
The Shopify integration is cleanly abstracted behind lib/shopify, meaning the storefront UI is decoupled from Shopify-specific API details. The next.config.ts image remotePatterns allowlist is appropriately scoped to cdn.shopify.com. No portability findings beyond CH-009 (experimental flags) are evidenced in the documentation.
| TD ID | Finding | Type | Severity | Effort | Screen(s) | Source |
|---|---|---|---|---|---|---|
| TD-001 | Duplicate getPage/getProduct/getCollection API calls in generateMetadata and page components | Design Debt | Major | Low | Item Detail, Product Detail, Search Detail | Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection]) |
| TD-002 | Unguarded product.featuredImage.url access in productJsonLd | Code Debt | Critical | Low | Product Detail | Product Detail (/product/[handle]) |
| TD-003 | dangerouslySetInnerHTML JSON-LD without </script> escape hardening | Code Debt | Critical | Low | Product Detail | Product Detail (/product/[handle]) |
| TD-004 | dangerouslySetInnerHTML in Prose component — XSS dependency on Shopify sanitization | Code Debt | Major | Medium | Item Detail | Item Detail (/[page]) |
| TD-005 | No error boundaries at page level for Shopify API failures | Design Debt | Major | Medium | All screens | All screens |
| TD-006 | Invalid Date rendered when page.updatedAt is malformed | Code Debt | Minor | Low | Item Detail | Item Detail (/[page]) |
| TD-007 | No test suite — test script runs only Prettier check | Test Debt | Major | Very High | All screens | package.json |
| TD-008 | No pagination on search and collection product listings | Design Debt | Major | High | Search, Search Detail | Search (/search), Search Detail (/search/[collection]) |
| TD-009 | Experimental Next.js features (ppr, inlineCss, useCache) on canary build | Dependency Debt | Minor | Low | All screens | next.config.ts |
| TD-010 | Unsafe searchParams type cast suppresses string[] and undefined awareness | Code Debt | Major | Low | Search, Search Detail | Search (/search), Search Detail (/search/[collection]) |
| TD-011 | Server locale used for date formatting — incorrect for international users | Code Debt | Major | Low | Item Detail | Item Detail (/[page]) |
| TD-012 | Missing <time> element for last-updated date | Code Debt | Minor | Low | Item Detail | Item Detail (/[page]) |
| TD-013 | Missing loading.tsx skeletons on Search, Search Detail, and Item Detail | Design Debt | Minor | Medium | Search, Search Detail, Item Detail | Search (/search), Search Detail (/search/[collection]), Item Detail (/[page]) |
| TD-014 | Absent ARIA live region for search result count announcements | Code Debt | Minor | Low | Search | Search (/search) |
| TD-015 | Hidden products accessible via direct URL — soft-hide not documented as intentional to merchants | Doc Debt | Info | Low | Product Detail | Product Detail (/product/[handle]) |
| Finding ID | Quality Dimension | Severity | Title | Screen(s) | Effort | Status |
|---|---|---|---|---|---|---|
| CH-002 | Security | Critical | dangerouslySetInnerHTML JSON-LD Without </script> Escape Hardening | Product Detail | Low | Open |
| CH-003 | Reliability | Critical | Unguarded featuredImage Property Access in JSON-LD | Product Detail | Low | Open |
| CH-001 | Maintainability | Major | Duplicate Shopify API Calls Across generateMetadata and Page Components | Item Detail, Product Detail, Search Detail | Low | Open |
| CH-004 | Reliability | Major | No Error Boundaries at Page Level — Shopify API Failures Produce Unhandled Server Errors | All screens | Medium | Open |
| CH-006 | Reliability | Major | No Test Suite Present Across All Assessed Screens | All screens | Very High | Open |
| CH-007 | Security | Major | dangerouslySetInnerHTML in Prose Component — XSS Dependency on Shopify Output Sanitization | Item Detail | Medium | Open |
| CH-008 | Performance Efficiency | Major | No Pagination on Search and Collection Product Listings | Search, Search Detail | High | Open |
| CH-010 | Maintainability | Major | Unsafe searchParams Type Cast Suppresses Runtime Safety | Search, Search Detail | Low | Open |
| CH-011 | Functional Suitability | Major | Server Locale Used for Date Formatting — Incorrect Output for International Users | Item Detail | Low | Open |
| CH-005 | Reliability | Minor | Invalid Date Rendered to Users When page.updatedAt Is Malformed | Item Detail | Low | Open |
| CH-009 | Compatibility | Minor | Experimental Next.js Features in Production Configuration | All screens | Low | Open |
| CH-013 | Usability | Minor | Missing <time> Element for Machine-Readable Dates | Item Detail | Low | Open |
| CH-014 | Usability | Minor | Missing Loading Skeletons on Search and Item Detail Screens | Search, Search Detail, Item Detail | Medium | Open |
| CH-015 | Usability | Minor | Absent ARIA Live Regions for Search Result Announcements | Search | Low | Open |
| CH-012 | Functional Suitability | Info | Hidden Products Accessible via Direct URL — Soft-Hide Is Not Access Control | Product Detail | Low | Open |
The following findings are inferred from patterns described in the documentation but cannot be confirmed without direct source code access. They are not counted in severity totals and are not scheduled in the Remediation Roadmap.
| Finding ID | Quality Dimension | Inferred Severity | Title | Inference Basis | Verification Action |
|---|---|---|---|---|---|
| CHV-001 | Reliability | Major | No root-level error.tsx or global-error.tsx boundary | (inferred from absence of mention in any screen's §10 or §17 — verify against app/error.tsx and app/global-error.tsx in the repository) | Search the repository for app/error.tsx and app/global-error.tsx; if present, reduce CH-004 severity to Minor |
| CHV-002 | Security | Major | Prose component uses dangerouslySetInnerHTML without HTML sanitization library | (inferred from html prop pattern described in Item Detail §3 — verify against components/prose.tsx source) | Inspect components/prose.tsx; if DOMPurify or equivalent is present, CH-007 can be closed |
| CHV-003 | Performance Efficiency | Major | getProducts and getCollectionProducts have no explicit product count limit in GraphQL queries | (inferred from absence of pagination documentation in Search §5 and Search Detail §5 — verify against lib/shopify GraphQL query definitions) | Search lib/shopify for first: argument in product connection queries; if ≤ 50, reduce CH-008 to Minor |
| CHV-004 | Maintainability | Minor | lib/shopify fetch calls do not use React.cache() for request-level memoization | (inferred from CH-001 documentation evidence — verify against lib/shopify.ts source for cache import from react) | Search lib/shopify.ts for import { cache } from 'react'; if present, CH-001 may already be mitigated |
| CHV-005 | Reliability | Minor | No observability SDK configured at the infrastructure level | (inferred from absence of Sentry, Datadog, or equivalent packages in package.json devDependencies and dependencies — verify against instrumentation.ts, sentry.client.config.ts, or equivalent files in the repository) | Search the repository for instrumentation.ts and any observability SDK config files; the absence of such packages in package.json is suggestive but not conclusive |
| CHV-006 | Usability | Minor | Carousel component lacks ARIA roles and keyboard navigation for WCAG 2.1 SC 2.2.2 compliance | (inferred from Home §15 accessibility note — verify against components/carousel.tsx source) | Inspect components/carousel.tsx for role="region", aria-label, keyboard event handlers, and pause controls |
| CHV-007 | Maintainability | Minor | ThreeItemGrid and Carousel components lack Suspense boundaries at the Home page level | (inferred from Home §11: "no such boundaries are present in the page component source" — verify whether child components define their own internal Suspense) | Inspect components/grid/three-items.tsx and components/carousel.tsx for internal <Suspense> wrappers |
Phase 1: Immediate (This Sprint)
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 1 | CH-003 | Add null guard for product.featuredImage in productJsonLd construction | Low | Eliminates 500 errors for products without a featured image |
| 2 | CH-002 | Replace JSON.stringify with safeJsonLd utility that escapes </script> sequences | Low | Closes XSS vector on the primary conversion page |
| 3 | CH-001 | Wrap getPage, getProduct, and getCollection with React.cache() in lib/shopify | Low | Eliminates duplicate Shopify API calls; reduces rate-limit risk |
| 4 | CH-010 | Replace searchParams type cast with explicit runtime type guards on both search screens | Low | Prevents unexpected string[] values reaching Shopify API |
Phase 2: Short-Term (30 Days)
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 5 | CH-007 | Inspect Prose component; add DOMPurify sanitization if not already present | Medium | Closes XSS dependency on Shopify CMS output |
| 6 | CH-004 | Add app/error.tsx and app/global-error.tsx; wrap RelatedProducts in an Error Boundary | Medium | Graceful degradation for Shopify API failures across all screens |
| 7 | CH-011 | Replace undefined locale with a fixed locale (e.g., 'en-US') or a client-side locale-aware <time> component | Low | Correct date formatting for all users |
| 8 | CH-013 | Replace plain <p> date with <time datetime={page.updatedAt}> element | Low | Semantic and accessibility improvement |
| 9 | CH-015 | Add role="status" and aria-live="polite" to search results summary paragraph | Low | WCAG 2.1 SC 4.1.3 compliance for screen reader users |
| 10 | CH-005 | Add isNaN guard before Intl.DateTimeFormat.format() call | Low | Prevents "Invalid Date" rendering on malformed Shopify timestamps |
Phase 3: Medium-Term (90 Days)
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 11 | CH-006 | Add Vitest + Playwright to the project; implement priority test cases (CH-002, CH-003, sort fallback, not-found handling) | Very High | Establishes regression safety net for all future changes |
| 12 | CH-014 | Add loading.tsx skeleton files for Search, Search Detail, and Item Detail routes | Medium | Eliminates blank-page experience during slow API responses |
| 13 | CH-009 | Document canary version rationale in README; establish upgrade process for experimental API stabilization | Low | Reduces upgrade risk and improves team awareness |
Phase 4: Long-Term (Backlog)
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 14 | CH-008 | Implement cursor-based pagination on Search and Search Detail using Shopify pageInfo | High | Scalability for large catalogs; improved Core Web Vitals |
| 15 | CH-012 | Document soft-hide behavior in merchant-facing guide; evaluate whether access restriction is required | Low | Prevents merchant expectation mismatch |
| Pattern | Screen(s) | Quality Dimension | Description | Recommendation |
|---|---|---|---|---|
| React Server Components used consistently | All screens | Performance Efficiency | All five assessed page components are RSCs with no "use client" directive, eliminating client-side JavaScript for page shells and reducing bundle size. | Maintain this pattern for all new page components; only introduce "use client" at the leaf component level where interactivity is required. |
| TypeScript strict mode with noUncheckedIndexedAccess | All screens | Maintainability | tsconfig.json enables strict: true and noUncheckedIndexedAccess: true, providing the strongest TypeScript safety guarantees available. This is above the default Next.js configuration. | Maintain these settings; do not disable them for convenience. The noUncheckedIndexedAccess flag in particular prevents a class of array index bugs. |
| Shopify integration cleanly abstracted behind lib/shopify | All screens | Maintainability / Portability | All Shopify API calls are mediated through lib/shopify functions (getPage, getProduct, getProducts, getCollection, getCollectionProducts). Page components contain no raw GraphQL or fetch calls. | Continue this pattern for all new Shopify API operations. This abstraction makes it straightforward to swap the commerce backend or add caching/retry logic in one place. |
| Server-side-only API credentials | All screens | Security | Shopify API tokens (SHOPIFY_STORE_DOMAIN, SHOPIFY_STOREFRONT_ACCESS_TOKEN) are referenced only within lib/shopify and are never prefixed with NEXT_PUBLIC_, ensuring they are never included in the client bundle. | Enforce this pattern via a lint rule (e.g., ESLint no-restricted-syntax on process.env.NEXT_PUBLIC_SHOPIFY_*) to prevent accidental credential exposure in future changes. |
| Streaming SSR via Suspense on Product Detail | Product Detail | Performance Efficiency | The Gallery and ProductDescription components are wrapped in Suspense boundaries, enabling Next.js to stream the page shell to the browser immediately while these subtrees resolve. This improves TTFB and perceived load performance on the primary conversion page. | Apply the same Suspense wrapping pattern to ThreeItemGrid and Carousel on the Home screen (verify via CHV-007). |
| Parallel data fetching via separate async server components | Product Detail | Performance Efficiency | RelatedProducts is a separate async server component that fetches getProductRecommendations concurrently with the main product card render, rather than sequentially. | Use this pattern (separate async server components rather than sequential await chains) for any future page that requires multiple independent data sources. |
| SEO metadata fallback chains | Item Detail, Product Detail, Search Detail | Functional Suitability | All three dynamic screens implement a two-level fallback chain for SEO title and description (seo.title → page.title, seo.description → page.bodySummary/product.description), ensuring meaningful metadata even when Shopify SEO fields are not populated. | Standardize this fallback pattern in a shared buildMetadata utility to avoid duplication across routes. |
| Sort parameter validation with silent fallback | Search, Search Detail | Reliability | The sort query parameter is validated against the sorting constants array before being passed to the Shopify API. Unrecognized values fall back to defaultSort, preventing invalid sort values from reaching the API. | This is a good defensive pattern. Extend it with the runtime type guard from CH-010 to also handle string[] inputs. |
| notFound() called in both generateMetadata and page component | Item Detail, Product Detail, Search Detail | Reliability | All three dynamic screens call notFound() in both generateMetadata and the page component body when the resource is not found, ensuring consistent 404 behavior regardless of which phase encounters the missing resource first. | Maintain this dual-guard pattern for all future dynamic routes. |
| Next.js metadata API for SEO — no third-party SEO package | All screens | Maintainability | SEO metadata (title, description, Open Graph, robots) is managed entirely through Next.js's built-in Metadata API and generateMetadata exports, with no third-party SEO library dependency. | Continue using the built-in API; it is sufficient for this use case and avoids an additional dependency. |
| Image optimization with AVIF/WebP and scoped remotePatterns | All screens | Performance Efficiency / Security | next.config.ts configures Next.js image optimization to serve AVIF and WebP formats, and restricts remote image sources to cdn.shopify.com/s/files/** — preventing the image optimization endpoint from being used as an open proxy. | If additional image CDNs are added (e.g., for user-generated content), add them explicitly to remotePatterns rather than using a wildcard hostname. |
| Screen | Route | Maintainability | Reliability | Security | Performance | Overall | Finding Count |
|---|---|---|---|---|---|---|---|
| Home | / | Good | Fair | Good | Good | Good | 1 |
| Item Detail | /[page] | Fair | Fair | Fair | Good | Fair | 7 |
| Product Detail | /product/[handle] | Good | Poor | Poor | Good | Fair | 5 |
| Search | /search | Fair | Fair | Good | Fair | Fair | 5 |
| Search Detail | /search/[collection] | Fair | Fair | Good | Fair | Fair | 4 |
Note: Finding counts per screen reflect the number of findings where that screen is listed as an affected screen. Findings affecting all screens (CH-004, CH-006, CH-009) are counted once per screen in the matrix but appear as a single finding in the Finding Inventory.
Quality Terms
| Term | Definition |
|---|---|
| ISO/IEC 25010:2023 | International standard defining a software product quality model with eight characteristics: Functional Suitability, Performance Efficiency, Compatibility, Usability, Reliability, Security, Maintainability, and Portability. |
| SQALE Method | Software Quality Assessment based on Lifecycle Expectations — a method for classifying and estimating the remediation cost of technical debt. Debt types include Design, Code, Test, Doc, and Dependency debt. |
| Blocker | SonarQube severity level: a defect that will cause production failure or data loss. Requires immediate fix. |
| Critical | SonarQube severity level: a severe code quality issue with high probability of causing bugs. Fix within current sprint. |
| Major | SonarQube severity level: a significant quality issue that degrades maintainability or reliability. Fix within 30 days. |
| Minor | SonarQube severity level: a code quality issue with limited impact. Fix within 90 days. |
| Info | SonarQube severity level: a best practice recommendation with no immediate risk. Consider for future work. |
| Fault Tolerance | ISO 25010 sub-characteristic of Reliability: the degree to which a system operates as intended despite hardware or software faults. |
| Functional Correctness | ISO 25010 sub-characteristic of Functional Suitability: the degree to which a product provides correct results with the needed degree of precision. |
| Reusability | ISO 25010 sub-characteristic of Maintainability: the degree to which an asset can be used in more than one system or in building other assets. |
| Analysability | ISO 25010 sub-characteristic of Maintainability: the degree of effectiveness with which it is possible to assess the impact of a change or diagnose deficiencies. |
| Testability | ISO 25010 sub-characteristic of Maintainability: the degree of effectiveness with which test criteria can be established and tests performed. |
| Time Behaviour | ISO 25010 sub-characteristic of Performance Efficiency: the degree to which response and processing times meet requirements under stated conditions. |
Domain Terms
| Term | Definition |
|---|---|
| Shopify Storefront API | Shopify's public-facing GraphQL API for reading storefront data (products, collections, pages, cart). Authenticated via a storefront access token. Used as the data source for all product and content data in this application. |
| Page handle | A URL-safe string identifier for a Shopify resource (page, product, or collection). Used as both the URL route segment and the Shopify API lookup key. |
| HIDDEN_PRODUCT_TAG | A string constant from lib/constants used as a Shopify product tag to mark products that should not be indexed by search engines. Does not restrict direct URL access. |
| featuredImage | The primary/hero image of a Shopify product. Distinct from the full images array. May be absent for products without a configured hero image. |
| priceRange | A Shopify object containing minVariantPrice and maxVariantPrice, representing the price spread across all product variants. |
| availableForSale | A Shopify boolean indicating whether any variant of a product is currently purchasable. |
| productRecommendations | Shopify's algorithmic related-product suggestions, returned by the productRecommendations Storefront API query given a product ID. |
| sortKey | A Shopify Storefront API enum value specifying the field by which products are sorted (e.g., PRICE, BEST_SELLING, CREATED_AT). |
| defaultSort | A constant from lib/constants defining the fallback sort configuration used when no valid sort query parameter is present. |
| sorting | An array of sort option objects from lib/constants, each containing a slug (URL-friendly name), sortKey, and reverse value. Maps user-facing sort options to Shopify API parameters. |
| Collection | A Shopify concept representing a named group of products (e.g., "Summer Sale", "Accessories"). Has a handle, title, description, and SEO metadata. |
| Headless commerce | An architecture where the storefront UI (this Next.js application) is decoupled from the commerce backend (Shopify), communicating via APIs rather than Shopify's built-in theme system. |
| page.body | The full HTML content of a Shopify page, as returned by the Storefront API. Contains merchant-authored rich text rendered as HTML. |
| page.bodySummary | A plain-text excerpt of the page body, used as a fallback for SEO meta description. |
Technical Terms
| Term | Definition |
|---|---|
| React Server Component (RSC) | A React component that renders exclusively on the server, producing HTML with no client-side JavaScript bundle for the component itself. All five assessed page components are RSCs. |
| App Router | The Next.js 13+ routing system based on the app/ directory, where page.tsx files define routes and components are RSCs by default. |
| generateMetadata | A Next.js App Router special export from a page file that runs server-side to produce <head> metadata (title, description, Open Graph tags) for a route. |
| notFound() | A Next.js App Router utility function that halts rendering and triggers the nearest not-found.tsx boundary or the default 404 response. |
| Streaming SSR | A Next.js rendering mode where the server sends HTML in chunks as components resolve, rather than waiting for the entire page to render before sending any bytes. Enabled by <Suspense> boundaries. |
| Partial Prerendering (PPR) | An experimental Next.js feature (ppr: true) that combines static and dynamic rendering within a single route — static shell is served from the CDN while dynamic holes are streamed in. |
| useCache | An experimental Next.js caching directive that provides fine-grained control over server-side data caching. Enabled via experimental.useCache: true in next.config.ts. |
| React.cache() | A React API that memoizes the result of a function call for the duration of a single server request, guaranteeing that identical calls within one render pass return the same result without re-executing. |
| dangerouslySetInnerHTML | A React prop that bypasses React's HTML escaping to inject raw HTML strings into the DOM. Requires careful sanitization to prevent XSS. |
| JSON-LD | JSON Linked Data — a structured data format embedded in a <script type="application/ld+json"> tag, used by search engines for rich result eligibility. |
| AggregateOffer | A Schema.org type used in JSON-LD to represent a product with multiple price points, specifying highPrice and lowPrice. |
| Intl.DateTimeFormat | A built-in JavaScript API for locale-sensitive date and time formatting. When instantiated with undefined as the locale on a server, uses the server's runtime locale rather than the user's browser locale. |
| noUncheckedIndexedAccess | A TypeScript compiler option that adds undefined to the type of array index access expressions, preventing a class of runtime errors from unchecked array reads. |
| AVIF / WebP | Modern image formats with superior compression compared to JPEG/PNG. Configured in next.config.ts as the preferred formats for Next.js image optimization. |
| remotePatterns | A Next.js image configuration option that restricts which external image URLs the built-in image optimization endpoint will process, preventing use as an open proxy. |
| Canary build | A pre-release version of Next.js that includes features not yet in the stable release. Subject to breaking changes without semantic versioning guarantees. |
| Prose component | A custom component (components/prose) that applies Tailwind CSS typography (@tailwindcss/typography) prose styles to raw HTML content, likely using dangerouslySetInnerHTML internally. |
| GridTileImage | An internal component that renders a product image tile with an overlaid label showing product title, price, and currency. Used in grid and listing contexts. |
| CartProvider | A React context provider (in components/cart/cart-context) that wraps the application in the root layout and provides cart state to all child components. |
| Full Route Cache | A Next.js App Router caching layer that stores the rendered HTML and RSC payload of statically rendered routes on the server/CDN, enabling fast repeated page loads. |