Code Health Assessment

myowjaYOY/commerce

Generated by DocAgent — automated codebase documentation analysis. Based on analysis of 3 screens. Subject matter expert review is recommended before distribution.

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


1. Executive Summary

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.


2. Assessment Methodology

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:

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


3. Quality Scorecard

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

4. Detailed Findings by Quality Dimension

[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.]


4.1 Maintainability (Score: 4/5)

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:

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
});

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:

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;

Effort: Low (< 1 hour per screen; 2 screens)

SQALE Characteristic: Reliability

Source Evidence: Search (/search), Search Detail (/search/[collection])


4.2 Reliability (Score: 3/5)

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 }),

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:

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:

// 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:

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:

3. Search sort fallback logic

4. Invalid Date guard for page.updatedAt

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])


4.3 Security (Score: 3/5)

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) }}
/>

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:

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])


4.4 Performance Efficiency (Score: 4/5)

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:

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:

[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:

Effort: Low (< 1 hour for documentation; Medium for migration when stable APIs land)

SQALE Characteristic: Portability

Source Evidence: next.config.ts, package.json


4.5 Functional Suitability (Score: 3/5)

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:

new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
.format(new Date(page.updatedAt))

  1. Use a <time> element with ISO datetime and format client-side in a "use client" wrapper component that reads navigator.language. This is the most correct approach for international storefronts but adds a client component.

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:

Effort: Low (< 1 hour for documentation; High if access restriction is required)

SQALE Characteristic: Maintainability

Source Evidence: Product Detail (/product/[handle])


4.6 Usability (Score: 3/5)

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:

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:

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)


4.7 Compatibility (Score: 4/5)

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.


4.8 Portability (Score: 4/5)

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.


5. Technical Debt Inventory

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])

6. Finding Inventory

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

7. Needs Verification

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

8. Remediation Roadmap

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

9. Positive Patterns

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.

10. Screen Health Matrix

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.


11. Glossary

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.