vercel/commerce
May 5, 2026
Application: commerce Document Title: Code Health Assessment Date: May 2026
Generated by Inkwell Forge — 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 architectural foundation is sound — React Server Components are used consistently and correctly, the Shopify integration is cleanly abstracted behind lib/shopify, and TypeScript strict mode is enabled throughout — but a cluster of recurring patterns across screens introduces reliability and maintainability risk that warrants structured remediation.
Key strengths include the disciplined use of the Next.js App Router RSC model (zero client-side JavaScript overhead on all five assessed screens), clean separation of data-fetching concerns behind the lib/shopify abstraction layer, and the consistent application of notFound() guards that prevent broken UI states for missing Shopify resources. The next.config.ts demonstrates deliberate use of experimental Next.js features (Partial Prerendering, inline CSS, the useCache API) that position the application well for performance at scale.
The three highest-risk findings are: [CH-002] Unguarded featuredImage Access in JSON-LD Construction (a null-dereference that will crash the Product Detail page render for any product without a featured image), [CH-001] Duplicate getPage/getProduct API Calls Across generateMetadata and Page Component (a cross-cutting pattern affecting three screens that doubles Shopify API calls per request when fetch deduplication is not active), and [CH-006] JSON.stringify Used for HTML-Embedded JSON-LD Without HTML-Character Escaping (a latent XSS vector on the Product Detail page if Shopify-sourced content contains </script> sequences). See the Finding Inventory for the complete catalog of findings by severity.
This assessment is based entirely on per-screen technical documentation. Static analysis tooling, runtime profiling, git history, and direct source code access were not available. Findings marked "(based on documentation — verify with static analysis)" or "(inferred from …)" require confirmation before scheduling remediation work.
ISO/IEC 25010:2023 — eight quality characteristics assessed where applicable: Maintainability, Reliability, Security, Performance Efficiency, Functional Suitability, Usability, Compatibility, and Portability.
SQALE method — findings are classified by remediation type (Design Debt, Code Debt, Test Debt, Doc Debt, Dependency Debt) and effort band (Low / Medium / High / Very High).
Aligned with SonarQube severity definitions:
| 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 |
This assessment covers 5 screens of the commerce application:
| Screen | Route |
|---|---|
| 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. This includes lib/shopify, components/product/product-description, components/product/gallery, components/grid/tile, components/layout/product-grid-items, components/grid, and all middleware and layout files beyond app/layout.tsx.
lib/shopify service layer is referenced extensively but not documented; findings about its behavior are marked with the appropriate inference markers.| Quality Dimension | Score | Rating | Key Finding | Findings Count |
|---|---|---|---|---|
| Maintainability | 3/5 | Fair | Duplicate API call pattern recurs across three screens; hardcoded magic numbers and unsafe type casts add fragility | 4 |
| Reliability | 3/5 | Fair | Unguarded null dereference on featuredImage will crash Product Detail for products without a hero image |
3 |
| Security | 4/5 | Good | JSON.stringify without HTML-character escaping creates a latent XSS vector in JSON-LD injection |
2 |
| Performance Efficiency | 3/5 | Fair | No pagination on Search and Search Detail; duplicate API calls per request; no loading skeleton on ProductDescription | 3 |
| Functional Suitability | 3/5 | Fair | Search Detail renders no collection heading; hardcoded SEO description is a template placeholder | 2 |
| Usability | 3/5 | Fair | Missing loading feedback for ProductDescription; no <time> element for dates; <span> used where <strong> is semantically correct |
3 |
| Compatibility | 4/5 | Good | Locale-dependent date rendering tied to server locale may produce inconsistent output across environments | 1 |
| Portability | 4/5 | Good | Canary Next.js version with three experimental flags introduces upgrade risk | 1 |
The five assessed screens follow a consistent architectural pattern — thin RSC compositor pages that delegate data fetching and rendering to child components. This is a positive structural choice. However, three recurring patterns degrade maintainability: (1) a duplicate API call pattern in generateMetadata / page component pairs that appears on three screens, (2) unsafe searchParams type assertions on two screens, and (3) a hardcoded magic number for the image gallery cap. The codebase has no co-located tests on any assessed screen, which is the most significant maintainability gap.
generateMetadata and Page ComponentSeverity: Major Quality Sub-Characteristic: Reusability / Analysability Screen(s): Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection]) Component(s): app/[page]/page.tsx, app/product/[handle]/page.tsx, app/search/[collection]/page.tsx
Description: On three screens, the same Shopify service function is called independently in both generateMetadata and the default page export: getPage on Item Detail, getProduct on Product Detail, and getCollection on Search Detail. Each call is a separate invocation with no shared result. If the underlying lib/shopify functions use the native fetch API with identical cache keys, Next.js request memoization will deduplicate them within a single render pass. If lib/shopify uses a non-fetch HTTP client (e.g., a custom GraphQL client or node-fetch), two separate Shopify API network requests are made per page load on each of these three routes. This doubles API quota consumption, increases latency, and makes the data-fetching logic harder to reason about.
Evidence:
/[page]): §5 — "getPage is called twice per request — once in generateMetadata and once in the Page component."/product/[handle]): §5 — "getProduct(params.handle) is called twice — once in generateMetadata and once in ProductPage."/search/[collection]): §5 — "getCollection is called in generateMetadata" and §17 — "getCollection is called twice."Risk: If lib/shopify does not use native fetch (a common pattern in Shopify integrations that use a custom GraphQL client), every page load on these three routes makes two Shopify API calls instead of one. Under load, this doubles API rate-limit consumption and increases p99 latency. It also creates a subtle consistency risk: if the Shopify data changes between the two calls (unlikely but possible during a deployment), generateMetadata and the page body could render different data.
Recommendation:
cache() function (available in React 19, which this project uses) at the lib/shopify call site or in a per-request cache module. This guarantees deduplication regardless of the underlying HTTP client.// lib/shopify/cached.ts
import { cache } from 'react';
import { getProduct as _getProduct } from './index';
export const getProduct = cache(_getProduct);getProduct, getPage, and getCollection in the affected page files with the cached variants.generateMetadata to the page component via a shared module-level cache, though React cache() is the idiomatic App Router solution.Effort: Medium (2–4 hours across all three screens)
SQALE Characteristic: Efficiency / Maintainability
Source Evidence: Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection])
searchParams Type AssertionSeverity: Major Quality Sub-Characteristic: Modifiability / Analysability Screen(s): Search (/search), Search Detail (/search/[collection]) Component(s): app/search/page.tsx, app/search/[collection]/page.tsx
Description: Both screens cast searchParams using as { [key: string]: string }. This assertion suppresses TypeScript's awareness that: (a) query parameter values can be string | string[] | undefined when the same key appears multiple times in a URL, and (b) searchParams itself may be undefined (the prop type on Search Detail is documented as Promise<...> | undefined). The cast bypasses the type system's protection, meaning a crafted URL like /search?q=shirt&q=pants would pass a string[] to getProducts as the query parameter, producing undefined behavior. On Search Detail, if searchParams is undefined, the destructuring will throw a runtime error.
Evidence:
/search): §14 — "searchParams is cast with as { [key: string]: string }, which means if q or sort are undefined, searchValue and sort will be undefined."/search): §17 — "Unsafe type cast: searchParams is cast as { [key: string]: string } using as, which suppresses TypeScript's awareness that query parameter values can be string | string[] | undefined."/search/[collection]): §10 — "The destructuring const { sort } = searchParams as { [key: string]: string } would throw a runtime error if searchParams is undefined."/search/[collection]): §17 — "Unsafe searchParams type assertion."Risk: A crafted URL with duplicate query keys could cause getProducts or getCollectionProducts to receive a string[] where a string is expected, potentially causing an unhandled exception or incorrect API query. On Search Detail, an undefined searchParams causes a runtime crash. Both scenarios would surface as 500 errors to the user.
Recommendation:
// Safe extraction pattern
const rawSort = Array.isArray(searchParams?.sort)
? searchParams.sort[0]
: searchParams?.sort;
const sort = typeof rawSort === 'string' ? rawSort : undefined;ReadonlyURLSearchParams pattern or the next/navigation useSearchParams hook in client components. For RSC pages, use the searchParams prop with proper optional chaining.q and sort parameters on both affected screens.Effort: Low (< 1 hour per screen; 2 hours total)
SQALE Characteristic: Reliability / Maintainability
Source Evidence: Search (/search), Search Detail (/search/[collection])
Severity: Minor Quality Sub-Characteristic: Modifiability Screen(s): Product Detail (/product/[handle]) Component(s): app/product/[handle]/page.tsx
Description: The expression product.images.slice(0, 5) uses the literal 5 as the maximum number of images passed to the Gallery component. This magic number has no named constant, no comment explaining the rationale, and no configuration entry. If the desired gallery size changes, a developer must locate this literal by searching the codebase rather than updating a single named constant.
Evidence: Product Detail (/product/[handle]): §8 — "Only the first 5 product images (product.images.slice(0, 5)) are passed to Gallery, regardless of how many images Shopify returns. This is a hard-coded business rule limiting gallery size." §17 — "Hard-coded image limit: product.images.slice(0, 5) is a magic number with no named constant or configuration."
Risk: Low immediate risk. The risk is a maintenance error: a future developer changes the gallery layout to support 6 images, updates the Gallery component, but misses the slice(0, 5) call in the page file, resulting in the 6th image never being displayed. The bug would be non-obvious because the Gallery component would appear to work correctly.
Recommendation:
lib/constants (alongside the existing HIDDEN_PRODUCT_TAG and sorting constants):// lib/constants.ts
export const MAX_GALLERY_IMAGES = 5;page.tsx:product.images.slice(0, MAX_GALLERY_IMAGES).map(...)Effort: Low (< 30 minutes)
SQALE Characteristic: Maintainability
Source Evidence: Product Detail (/product/[handle])
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 page components
Description: No co-located tests were found for any of the five assessed screens. The package.json test script runs only prettier:check — a formatting check, not a test suite. No testing framework (Jest, Vitest, Playwright, Cypress) appears in the project manifest. This means there is no automated regression safety net for any of the documented screens. The documentation for each screen provides detailed recommended testing strategies (unit, integration, E2E), indicating the team is aware of the gap.
Evidence:
package.json: "test": "pnpm prettier:check" — no test runner configured.jest, vitest, @playwright/test, or cypress entries in dependencies or devDependencies.Risk: Without automated tests, any refactoring of the Shopify service layer, sort logic, metadata generation, or routing behavior carries a high risk of silent regression. The duplicate API call pattern (CH-001), the null-dereference risk (CH-002), and the type assertion issues (CH-003) would all benefit from test coverage before remediation to prevent introducing new bugs.
Recommendation:
@testing-library/react is the recommended choice for unit and integration tests; Playwright for E2E.generateMetadata fallback chains, notFound() guard behavior, sort resolution logic, and the productJsonLd construction.test script in package.json so CI enforces test passage alongside formatting.Effort: Very High (16+ hours to establish the framework and initial coverage across all five screens)
SQALE Characteristic: Testability
Source Evidence: All five assessed screens (§16); package.json
The screens handle the most common failure mode (missing Shopify resource) consistently and correctly via notFound() guards. However, one confirmed null-dereference bug will crash the Product Detail page for any product without a featured image, and the uniform treatment of "not found" and "API error" as the same condition makes operational diagnosis difficult. No error tracking SDK is identifiable from the project manifest.
featuredImage Access in JSON-LD ConstructionSeverity: Critical Quality Sub-Characteristic: Fault Tolerance Screen(s): Product Detail (/product/[handle]) Component(s): app/product/[handle]/page.tsx — productJsonLd construction block
Description: The productJsonLd object accesses product.featuredImage.url without a null guard. The generateMetadata function in the same file correctly guards this access with product.featuredImage || {}, but the page body's JSON-LD construction does not apply the same pattern. If a Shopify product has no featured image (a valid Shopify state), product.featuredImage will be null or undefined, and accessing .url on it will throw a TypeError at render time, crashing the entire page render and returning a 500 error to the user.
Evidence: Product Detail (/product/[handle]): §10 — "Missing featured image: The productJsonLd object accesses product.featuredImage.url without a null guard. If featuredImage is null or undefined, this will throw a runtime error." §17 — "Unguarded featuredImage access in JSON-LD: product.featuredImage.url in productJsonLd has no null guard, unlike the generateMetadata function which uses product.featuredImage || {}."
Risk: Any product in the Shopify catalog that lacks a featured image will produce a 500 error on its product detail page. This is a production-impacting bug, not a theoretical risk. The inconsistency with generateMetadata (which correctly guards the same field) suggests this was an oversight rather than an intentional design choice.
Recommendation:
generateMetadata:const productJsonLd = {
// ...
image: product.featuredImage?.url,
offers: {
// ...
},
};image field from the JSON-LD object when featuredImage is absent:...(product.featuredImage && { image: product.featuredImage.url }),productJsonLd with a product where featuredImage is null and asserts no exception is thrown (see CH-010).Effort: Low (< 30 minutes)
SQALE Characteristic: Reliability
Source Evidence: Product Detail (/product/[handle])
Severity: Minor Quality Sub-Characteristic: Fault Tolerance / Analysability Screen(s): Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection]) Component(s): app/[page]/page.tsx, app/product/[handle]/page.tsx, app/search/[collection]/page.tsx
Description: On all three screens that call notFound(), both a "resource not found in Shopify" condition and a "Shopify API network/infrastructure failure" condition result in the same notFound() call (or an unhandled exception that surfaces as a generic 500). Users and monitoring systems cannot distinguish between a legitimately missing page/product and a Shopify API outage from the rendered output. During an outage, users would see 404 pages for products that actually exist, which is misleading and could suppress incident detection.
Evidence:
/[page]): §10 — "Both 'page not found' and 'API error' scenarios result in the same notFound() call, making it impossible for users or monitoring systems to distinguish between a missing page and a Shopify API outage." §17 — "No error differentiation."/product/[handle]): §10 — "If getProduct throws, Next.js will render the error boundary (error.tsx) if present."/search/[collection]): §10 — "Shopify API network failure: Unhandled at this level — would surface as an unhandled server exception."Risk: During a Shopify API outage, product and content pages return 404 responses. This suppresses the true error signal, makes incident detection harder, and could cause SEO crawlers to de-index pages that are temporarily unavailable rather than permanently removed.
Recommendation:
lib/shopify, distinguish between a "not found" response (Shopify returns null data for a valid query) and a network/API error (thrown exception or non-200 HTTP status). Return a typed result or throw a typed error class.notFound() only for genuine 404 conditions; allow API errors to propagate to the error.tsx boundary, which can display a "temporarily unavailable" message.// In page component
try {
const page = await getPage(params.page);
if (!page) notFound();
// render...
} catch (err) {
if (err instanceof ShopifyNotFoundError) notFound();
throw err; // propagate to error.tsx
}Effort: High (4–8 hours, primarily in lib/shopify to introduce typed error classes)
SQALE Characteristic: Reliability
Source Evidence: Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection])
Severity: Minor Quality Sub-Characteristic: Analysability Screen(s): All five assessed screens Component(s): All page components; package.json
Description: No error tracking, application performance monitoring, or logging SDK is identifiable in the project manifest (package.json). No observability-related calls appear in any of the five documented screen files. This means that server-side rendering errors, Shopify API failures, and null-dereference crashes (such as CH-002) may go undetected until a user reports them.
Evidence:
package.json: No packages matching common observability SDKs (e.g., @sentry/nextjs, @datadog/browser-rum, newrelic, @opentelemetry/sdk-node) appear in dependencies or devDependencies.console.log, console.warn, console.error, error tracking service calls (Sentry, Datadog, etc.), or analytics event calls are present in this screen's component file."Risk: Production errors on any of the five assessed routes will be invisible to the engineering team unless a user reports them or the team actively monitors server logs. The null-dereference bug in CH-002 is a concrete example of an error that would be silent without instrumentation.
Recommendation:
instrumentation.ts) before treating this as a gap. [Not documented — WHO: DevOps/platform engineer; WHAT: Is error tracking (e.g., Sentry, Datadog, Vercel observability) configured at the infrastructure or instrumentation.ts level?; WHERE: Insert confirmation or finding upgrade in this section.]@sentry/nextjs or Vercel's native observability integration are the lowest-friction options.Effort: Medium (2–4 hours to integrate an SDK; Low if Vercel observability is already active)
SQALE Characteristic: Maintainability / Reliability
Source Evidence: All five assessed screens (§10); package.json
The security posture of the assessed screens is generally strong. All screens are React Server Components, which eliminates the risk of leaking server-side secrets to the client bundle. No user input is processed on any screen except as URL parameters passed to the Shopify API abstraction layer. The two findings below are a latent XSS vector in JSON-LD injection and a minor type-safety bypass in URL parameter handling (the latter is also captured under Maintainability as CH-003).
[Diagram: Security boundary diagram showing the RSC server boundary, lib/shopify abstraction layer, and Shopify Storefront API — illustrating where credentials are held and where user-supplied input enters the system]
JSON.stringify Without HTML-Character Escaping in JSON-LD InjectionSeverity: Critical Quality Sub-Characteristic: Security — Integrity Screen(s): Product Detail (/product/[handle]) Component(s): app/product/[handle]/page.tsx — JSON-LD <script> block
Description: The Product Detail page injects structured data into the page using dangerouslySetInnerHTML={{ __html: JSON.stringify(productJsonLd) }}. JSON.stringify does not escape HTML special characters such as <, >, &, or /. If a Shopify product's title, description, or any other field included in productJsonLd contains the sequence </script>, the browser's HTML parser will interpret it as closing the <script> tag, potentially allowing injected content to execute as JavaScript. While Shopify sanitizes merchant-authored content, this assumption is a single point of failure: a compromised Shopify account, a supply-chain issue in Shopify's content pipeline, or a future change to Shopify's sanitization policy would expose this vector.
Evidence: Product Detail (/product/[handle]): §14 — "JSON.stringify does not escape HTML special characters like <, >, or & by default, which could theoretically allow script injection if a product title or description contained </script> sequences. A safer approach would use a JSON serializer that escapes these characters."
Risk: If exploited, this could allow arbitrary JavaScript execution in the user's browser (XSS), enabling session hijacking, credential theft, or malicious redirects. The risk is currently mitigated by Shopify's content sanitization, but defense-in-depth requires that the application not rely solely on the upstream provider's sanitization.
Recommendation:
JSON.stringify with a serializer that escapes HTML special characters. A minimal, dependency-free approach:function safeJsonLd(data: unknown): string {
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026')
.replace(/'/g, '\\u0027');
}JSON.stringify in the dangerouslySetInnerHTML call:<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: safeJsonLd(productJsonLd) }}
/>
serialize-javascript npm package provides a battle-tested implementation if a dependency is acceptable.Effort: Low (< 1 hour)
SQALE Characteristic: Security
Source Evidence: Product Detail (/product/[handle])
page.body HTML Rendered Without Application-Level SanitizationSeverity: Minor Quality Sub-Characteristic: Security — Integrity Screen(s): Item Detail (/[page]) Component(s): app/[page]/page.tsx, components/prose
Description: The page.body field — a raw HTML string authored in Shopify's admin — is passed directly to the Prose component via an html prop. The documentation notes that the Prose component likely uses dangerouslySetInnerHTML to render this content. No application-level HTML sanitization is applied before rendering. The safety of this pattern depends entirely on Shopify's server-side sanitization of page body content. This is a defense-in-depth gap: if Shopify's sanitization is bypassed (e.g., via a compromised merchant account or a Shopify platform vulnerability), the application has no secondary protection.
Evidence: Item Detail (/[page]): §14 — "The safety of this rendering depends entirely on the Prose component's implementation — if it uses dangerouslySetInnerHTML without sanitization, XSS is theoretically possible if Shopify's content pipeline is compromised. In practice, Shopify sanitizes page body content server-side, but this screen performs no additional sanitization."
Risk: Severity is rated Minor (not Critical) because: (a) Shopify's content sanitization is a well-established, actively maintained control; (b) the content is merchant-authored, not user-submitted; and (c) this is a server-rendered page with no client-side state that could be exfiltrated. The risk is real but requires a multi-layer compromise to exploit.
Recommendation:
Prose component's implementation to confirm whether it uses dangerouslySetInnerHTML and whether any sanitization is applied. [Not documented — WHO: the developer who owns components/prose; WHAT: Does Prose use dangerouslySetInnerHTML? Is any HTML sanitization (e.g., DOMPurify) applied before rendering?; WHERE: Insert finding severity adjustment in this section based on the answer.]Prose uses dangerouslySetInnerHTML without sanitization, consider adding a server-side sanitization step using a library such as isomorphic-dompurify before passing content to Prose.Effort: Medium (2–4 hours to add sanitization; Low to add documentation comment)
SQALE Characteristic: Security
Source Evidence: Item Detail (/[page])
The RSC-first architecture is a strong performance foundation: zero client-side JavaScript on all five assessed screens, full-route caching via Next.js, and AVIF/WebP image optimization configured in next.config.ts. The three performance findings below are architectural gaps rather than implementation errors: the absence of pagination on product listing screens, the duplicate API call pattern (also captured as CH-001), and the missing loading skeleton for ProductDescription.
[Diagram: Data flow diagram for Product Detail page showing parallel vs. sequential fetch paths — getProduct (called twice), getProductRecommendations, and the Suspense streaming boundaries]
Severity: Major Quality Sub-Characteristic: Time Behaviour / Resource Utilisation Screen(s): Search (/search), Search Detail (/search/[collection]) Component(s): app/search/page.tsx, app/search/[collection]/page.tsx
Description: Both product listing screens fetch and render all matching products in a single Shopify API request. Shopify's Storefront API caps results at 250 products per request. For stores with large catalogs or popular collections, this means: (a) the Shopify API response payload is large, (b) the server-rendered HTML is large, (c) the Time to First Byte increases proportionally with catalog size, and (d) the browser must parse and render a large DOM. No pagination, infinite scroll, or cursor-based loading is implemented at either screen level.
Evidence:
/search): §12 — "The getProducts call fetches all matching products in a single request. There is no pagination, infinite scroll, or virtual list implementation." §17 — "No pagination: All matching products are fetched and rendered in a single pass. For stores with large catalogs, this is a scalability concern."/search/[collection]): §12 — "The screen fetches and renders all products in the collection in a single request." §17 — "No pagination: All products are fetched and rendered in a single request."Risk: For stores with more than ~50 products per collection or search result, page load times will degrade noticeably. At 250 products (Shopify's API limit), the rendered HTML and image loading will be substantial. This is a scalability cliff: the application works well for small catalogs but degrades predictably as the catalog grows.
Recommendation:
pageInfo.hasNextPage and endCursor fields. This is the idiomatic Shopify pagination pattern.?page=2 or ?after=<cursor>) so paginated results are deep-linkable and server-rendered.getProducts and getCollectionProducts to a reasonable limit (e.g., 24 or 48) and add a "Load More" or paginated navigation UI.Effort: Very High (16+ hours to implement cursor-based pagination across both screens and the lib/shopify service layer)
SQALE Characteristic: Efficiency
Source Evidence: Search (/search), Search Detail (/search/[collection])
ProductDescription Suspense BoundarySeverity: Minor Quality Sub-Characteristic: Time Behaviour / Usability Screen(s): Product Detail (/product/[handle]) Component(s): app/product/[handle]/page.tsx — ProductDescription Suspense boundary
Description: The Suspense boundary wrapping ProductDescription uses fallback={null}, meaning no placeholder UI is shown while the component resolves. The Gallery component correctly uses a blank square placeholder to prevent layout shift, but the right-side panel (title, price, variant selectors, add-to-cart) renders nothing during loading. On slow connections, the product image appears but the purchase UI is absent, which is disorienting and may cause users to believe the page is broken.
Evidence: Product Detail (/product/[handle]): §17 — "No loading feedback for ProductDescription: The Suspense fallback for ProductDescription is null, meaning users see nothing while the component loads. This could cause layout shift and a poor experience on slow connections."
Risk: On slow connections or during high server load, users see a product image with no title, price, or add-to-cart button. This degrades the purchase experience and may increase bounce rate on the most commercially important page in the application.
Recommendation:
ProductDescription that mirrors the component's layout: a placeholder for the title (e.g., a gray rectangle), a placeholder for the price, and a disabled-state button placeholder.animate-pulse utility for the skeleton effect, consistent with the existing blank-square placeholder used for Gallery.<Suspense fallback={<ProductDescriptionSkeleton />}>
<ProductDescription product={product} />
</Suspense>
Effort: Medium (1–3 hours to design and implement the skeleton component)
SQALE Characteristic: Usability / Efficiency
Source Evidence: Product Detail (/product/[handle])
The five screens implement their documented feature sets correctly. The two findings in this dimension are a missing page-level heading on Search Detail (a functional gap that affects both usability and SEO) and a hardcoded placeholder SEO description on the Home screen that should be replaced before production use.
Severity: Major Quality Sub-Characteristic: Functional Completeness Screen(s): Search Detail (/search/[collection]) Component(s): app/search/[collection]/page.tsx
Description: The Search Detail page renders no <h1> or collection title in the page body. The collection name appears only in the browser tab title (via generateMetadata), but the page body contains only a product grid or an empty-state message. Users have no visual confirmation of which collection they are viewing. This is both a functional gap (users cannot confirm their navigation context) and an accessibility issue (no page heading for screen readers).
Evidence: Search Detail (/search/[collection]): §17 — "No collection heading rendered: The page body renders no <h1> or collection title, which is both an accessibility concern (no page heading) and a UX gap (users have no visual confirmation of which collection they are viewing beyond the browser tab title)."
Risk: Users who navigate to a collection page from a menu or search filter have no on-page confirmation of their location. If the collection name is not visible in the surrounding navigation (e.g., on mobile where the nav is collapsed), the page context is entirely ambiguous. This also means the page has no <h1>, which is a significant SEO signal gap for collection pages.
Recommendation:
getCollection call in generateMetadata already fetches the collection title; use React's cache() (see CH-001) to share this data with the page component without an additional API call.<h1> above the product grid:<h1 className="text-2xl font-bold mb-6">{collection.title}</h1>
description, consider rendering it below the heading for additional context and SEO value.Effort: Low (< 1 hour, assuming CH-001 is addressed first to avoid a third getCollection call)
SQALE Characteristic: Functional Completeness
Source Evidence: Search Detail (/search/[collection])
Severity: Minor Quality Sub-Characteristic: Functional Correctness Screen(s): Home (/) Component(s): app/page.tsx — metadata export
Description: The metadata export on the Home screen contains a hardcoded description string: "High-performance ecommerce store built with Next.js, Vercel, and Shopify." This is a generic stack description from the Next.js Commerce starter template, not a meaningful, store-specific SEO description. It will appear in Google search results and social media link previews for the store's homepage, presenting a technical description rather than brand or product copy.
Evidence: Home (/): §17 — "The description in the metadata export is hardcoded as a generic stack description ('High-performance ecommerce store built with Next.js, Vercel, and Shopify.') rather than a meaningful, store-specific SEO description. This is likely a placeholder from a starter template and should be updated with actual brand/store copy before production use."
Risk: Low technical risk. The business risk is that the store's homepage appears in search results with a developer-facing description rather than a customer-facing one, reducing click-through rates from organic search.
Recommendation:
SITE_DESCRIPTION) or from a Shopify shop metadata query.SITE_NAME environment variable is already used in the same metadata export — apply the same pattern for description:const { SITE_NAME, SITE_DESCRIPTION } = process.env;
export const metadata = {
description: SITE_DESCRIPTION,
// ...
};Effort: Low (< 30 minutes)
SQALE Characteristic: Functional Correctness
Source Evidence: Home (/)
The screens handle the most common empty and error states correctly (no-results messages, notFound() for missing resources). The usability findings below are a cluster of smaller gaps: a missing semantic <time> element, a <span> used where <strong> is semantically correct, and the missing ProductDescription loading skeleton (also captured under Performance as CH-011).
<time> Element for Machine-Readable DateSeverity: Minor Quality Sub-Characteristic: Accessibility / Usability Screen(s): Item Detail (/[page]) Component(s): app/[page]/page.tsx — last-updated paragraph
Description: The last-updated date on the Item Detail screen is rendered as a plain formatted string inside a <p> tag. The HTML <time> element with a datetime attribute is the semantically correct element for dates and times — it provides a machine-readable ISO 8601 timestamp alongside the human-readable display string, which assistive technologies, search engines, and browser extensions can consume.
Evidence: Item Detail (/[page]): §15 — "The Intl.DateTimeFormat date string is rendered as visible text only; there is no <time> element with a datetime attribute, which would be more semantically correct and machine-readable for assistive technologies." §17 — "No <time> element for the updated date."
Risk: Low. Screen readers can read the formatted date string. The gap is semantic correctness and machine-readability, not a functional failure.
Recommendation:
<time> element:<p className="text-sm italic">
{`${page.title} 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>
'en-US'.Effort: Low (< 30 minutes)
SQALE Characteristic: Usability
Source Evidence: Item Detail (/[page])
<span> Used for Semantic Emphasis Where <strong> Is CorrectSeverity: Info Quality Sub-Characteristic: Usability / Accessibility Screen(s): Search (/search) Component(s): app/search/page.tsx — results summary paragraph
Description: The search term in the results summary paragraph is wrapped in <span className="font-bold"> for visual bold styling. The semantically correct element for text that has strong importance or emphasis is <strong>, which conveys semantic meaning to screen readers and search engines in addition to the visual weight. Using <span> with a CSS class achieves the visual effect but loses the semantic signal.
Evidence: Search (/search): §15 — "The search term is wrapped in <span className='font-bold'> — a presentational element. Using <strong> instead would convey semantic emphasis to screen readers."
Risk: Minimal. Screen readers will still read the search term; they simply will not announce it as emphasized. This is a best-practice gap, not a functional failure.
Recommendation:
<span className="font-bold"> with <strong> (which is bold by default in browsers, so the font-bold class may be removable, or can be retained for Tailwind reset compatibility):<strong>{searchValue}</strong>
Effort: Low (< 15 minutes)
SQALE Characteristic: Usability
Source Evidence: Search (/search)
The application targets modern browsers via the es2015 TypeScript compilation target and uses CSS features (Tailwind v4, container queries) that have broad modern browser support. One compatibility concern is identified: locale-dependent date rendering tied to the server's runtime locale.
Severity: Minor Quality Sub-Characteristic: Co-existence / Adaptability Screen(s): Item Detail (/[page]) Component(s): app/[page]/page.tsx — date formatting
Description: The updatedAt date is formatted using new Intl.DateTimeFormat(undefined, { ... }). Passing undefined as the locale instructs the runtime to use the system/environment default locale. On a Vercel deployment, the Node.js runtime locale is typically en-US, but this is not guaranteed across all deployment environments, regions, or future infrastructure changes. If the server locale changes, the date format changes silently (e.g., from "January 15, 2024" to "15. Januar 2024"), which may be unexpected for an English-language storefront.
Evidence: Item Detail (/[page]): §8 — "Using undefined as the locale argument to Intl.DateTimeFormat ... may differ from the user's locale and could produce inconsistent output across deployment environments." §17 — "Locale-dependent date rendering: Using undefined as the locale for Intl.DateTimeFormat ties the date format to the server's runtime locale."
Risk: Low in practice on Vercel, but the behavior is environment-dependent and non-deterministic across deployment targets. A future infrastructure change or multi-region deployment could produce inconsistent date formats.
Recommendation:
'en-US' (or the store's primary language) to make the output deterministic:new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(new Date(page.updatedAt))Accept-Language header or a locale context, rather than relying on the server default.Effort: Low (< 15 minutes)
SQALE Characteristic: Portability / Compatibility
Source Evidence: Item Detail (/[page])
The application is well-positioned for portability: the Shopify integration is cleanly abstracted behind lib/shopify, environment variables are server-only, and the RSC architecture avoids client-side framework lock-in. One portability concern is the use of three experimental Next.js features in next.config.ts on a canary release.
Severity: Minor Quality Sub-Characteristic: Installability / Adaptability Screen(s): All five assessed screens Component(s): next.config.ts, package.json
Description: The project uses next: "15.6.0-canary.60" — a canary (pre-release) build of Next.js — with three experimental flags enabled: ppr: true (Partial Prerendering), inlineCss: true, and useCache: true. Canary releases are not production-stable; APIs, behavior, and configuration options can change between canary versions without a semver major bump. The useCache API in particular is documented as experimental and subject to change. Running a canary build in production means the application may break on a routine pnpm update if the canary API surface changes.
Evidence: package.json: "next": "15.6.0-canary.60". next.config.ts: experimental: { ppr: true, inlineCss: true, useCache: true }.
Risk: Moderate upgrade risk. If the team updates Next.js (e.g., to a newer canary or to the stable 15.x release), any of the three experimental APIs could have breaking changes. The useCache API is the highest-risk flag as it is the newest and most likely to change. This is not an immediate production risk if the version is pinned, but it creates upgrade debt.
Recommendation:
package.json (use "15.6.0-canary.60" without the ^ caret) to prevent accidental upgrades.next.config.ts with a comment explaining what it enables and when it can be removed.ppr, inlineCss, and useCache before any upgrade.Effort: Low (< 1 hour to pin and document; Medium for the eventual stable migration)
SQALE Characteristic: Portability
Source Evidence: package.json, next.config.ts
| TD ID | Finding | Type | Severity | Effort | Screen(s) | Source |
|---|---|---|---|---|---|---|
| TD-001 | Duplicate getPage/getProduct/getCollection calls in generateMetadata and page component |
Design Debt | Major | Medium | Item Detail, Product Detail, Search Detail | Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection]) |
| TD-002 | Unguarded featuredImage.url access in productJsonLd construction |
Code Debt | Critical | Low | Product Detail | Product Detail (/product/[handle]) |
| TD-003 | Unsafe searchParams type assertion bypasses TypeScript safety |
Code Debt | Major | Low | Search, Search Detail | Search (/search), Search Detail (/search/[collection]) |
| TD-004 | Hardcoded magic number 5 for gallery image cap |
Code Debt | Minor | Low | Product Detail | Product Detail (/product/[handle]) |
| TD-005 | No pagination on product listing screens | Design Debt | Major | Very High | Search, Search Detail | Search (/search), Search Detail (/search/[collection]) |
| TD-006 | JSON.stringify without HTML-character escaping in JSON-LD injection |
Code Debt | Critical | Low | Product Detail | Product Detail (/product/[handle]) |
| TD-007 | page.body HTML rendered without application-level sanitization |
Design Debt | Minor | Medium | Item Detail | Item Detail (/[page]) |
| TD-008 | No differentiation between "not found" and "API error" conditions | Design Debt | Minor | High | Item Detail, Product Detail, Search Detail | Item Detail (/[page]), Product Detail (/product/[handle]), Search Detail (/search/[collection]) |
| TD-009 | No observability SDK identifiable in project manifest | Design Debt | Minor | Medium | All screens | All screens; package.json |
| TD-010 | No co-located tests; test script runs only Prettier |
Test Debt | Major | Very High | All screens | All screens; package.json |
| TD-011 | Missing loading skeleton for ProductDescription Suspense boundary |
Code Debt | Minor | Medium | Product Detail | Product Detail (/product/[handle]) |
| TD-012 | Search Detail renders no collection heading (<h1>) |
Code Debt | Major | Low | Search Detail | Search Detail (/search/[collection]) |
| TD-013 | Hardcoded template placeholder SEO description on Home screen | Doc Debt | Minor | Low | Home | Home (/) |
| TD-014 | Missing <time> element for machine-readable date |
Code Debt | Minor | Low | Item Detail | Item Detail (/[page]) |
| TD-015 | <span> used for semantic emphasis instead of <strong> |
Code Debt | Info | Low | Search | Search (/search) |
| TD-016 | Locale-dependent date rendering tied to server runtime locale | Code Debt | Minor | Low | Item Detail | Item Detail (/[page]) |
| TD-017 | Canary Next.js version with three experimental flags | Dependency Debt | Minor | Low (pin) / Medium (migrate) | All screens | package.json, next.config.ts |
| Finding ID | Quality Dimension | Severity | Title | Screen(s) | Effort | Status |
|---|---|---|---|---|---|---|
| CH-001 | Maintainability | Major | Duplicate API Calls in generateMetadata and Page Component |
Item Detail, Product Detail, Search Detail | Medium | Open |
| CH-002 | Reliability | Critical | Unguarded featuredImage Access in JSON-LD Construction |
Product Detail | Low | Open |
| CH-003 | Maintainability | Major | Unsafe searchParams Type Assertion |
Search, Search Detail | Low | Open |
| CH-004 | Maintainability | Minor | Hardcoded Magic Number for Gallery Image Cap | Product Detail | Low | Open |
| CH-005 | Performance Efficiency | Major | No Pagination on Product Listing Screens | Search, Search Detail | Very High | Open |
| CH-006 | Security | Critical | JSON.stringify Without HTML-Character Escaping in JSON-LD Injection |
Product Detail | Low | Open |
| CH-007 | Security | Minor | page.body HTML Rendered Without Application-Level Sanitization |
Item Detail | Medium | Open |
| CH-008 | Reliability | Minor | No Differentiation Between "Not Found" and "API Error" | Item Detail, Product Detail, Search Detail | High | Open |
| CH-009 | Reliability | Minor | No Observability SDK Identifiable in Project Manifest | All screens | Medium | Open |
| CH-010 | Maintainability | Major | No Co-Located Tests on Any Assessed Screen | All screens | Very High | Open |
| CH-011 | Performance Efficiency | Minor | Missing Loading Skeleton for ProductDescription Suspense Boundary |
Product Detail | Medium | Open |
| CH-012 | Functional Suitability | Major | Search Detail Renders No Collection Heading | Search Detail | Low | Open |
| CH-013 | Functional Suitability | Minor | Hardcoded Template Placeholder in Home Screen SEO Metadata | Home | Low | Open |
| CH-014 | Usability | Minor | Missing <time> Element for Machine-Readable Date |
Item Detail | Low | Open |
| CH-015 | Compatibility | Minor | Locale-Dependent Date Rendering Tied to Server Runtime Locale | Item Detail | Low | Open |
| CH-016 | Usability | Info | <span> Used for Semantic Emphasis Where <strong> Is Correct |
Search | Low | Open |
| CH-017 | Portability | Minor | Canary Next.js Version with Three Experimental Flags | All screens | Low | Open |
The following findings are based on inference from technology patterns or incomplete documentation evidence. They are not counted in severity totals and are not scheduled in the Remediation Roadmap. Each requires a specific verification action before it can be promoted to the main Finding Inventory.
| Finding ID | Quality Dimension | Inferred Severity | Title | Inference Basis | Verification Action |
|---|---|---|---|---|---|
| CHV-001 | Security | Minor | Prose Component Uses dangerouslySetInnerHTML Without Sanitization |
(inferred from component name and html prop pattern — verify against components/prose source) |
Inspect components/prose to confirm whether dangerouslySetInnerHTML is used and whether any sanitization is applied. If confirmed unsanitized, promote CH-007 severity to Major. |
| CHV-002 | Reliability | Minor | lib/shopify Uses Non-fetch HTTP Client, Disabling Next.js Request Memoization |
(inferred from Next.js App Router deduplication documentation — verify against lib/shopify source) |
Inspect lib/shopify to determine whether getPage, getProduct, getCollection, and getCollectionProducts use the native fetch API. If a non-fetch client is used, CH-001 severity should be upgraded to Critical. |
| CHV-003 | Performance Efficiency | Minor | getCollectionProducts Has No Explicit Cache Configuration |
(inferred from Next.js 15 default dynamic rendering behavior — verify against lib/shopify source) |
Inspect lib/shopify getCollectionProducts and getProducts for next.revalidate or cache options. If absent, these calls are fully dynamic (no caching), which is a performance concern for high-traffic stores. |
| CHV-004 | Reliability | Info | No error.tsx Co-Located with Search or Search Detail Routes |
(inferred from §17 of Search screen — "No explicit error boundary" — verify against app/search/ directory structure) |
Check whether app/search/error.tsx and app/search/[collection]/error.tsx exist. If absent, API errors on these routes fall through to the root error boundary, producing a full-page error for a partial failure. |
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 1 | CH-002 | Add null guard to product.featuredImage.url in productJsonLd construction in app/product/[handle]/page.tsx |
Low | Eliminates production crash for products without a featured image |
| 2 | CH-006 | Replace JSON.stringify with an HTML-character-escaping serializer in the JSON-LD <script> block |
Low | Closes latent XSS vector in JSON-LD injection |
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 1 | CH-001 | Wrap getPage, getProduct, and getCollection in React cache() to deduplicate calls across generateMetadata and page components |
Medium | Eliminates duplicate Shopify API calls on three routes; reduces API quota consumption and latency |
| 2 | CH-003 | Replace as { [key: string]: string } type assertions with safe optional-chaining extraction on Search and Search Detail |
Low | Prevents runtime crash on undefined searchParams and incorrect behavior on duplicate query keys |
| 3 | CH-012 | Add <h1> collection title to Search Detail page body (depends on CH-001 to avoid a third getCollection call) |
Low | Provides user navigation context; adds SEO <h1> signal to collection pages |
| 4 | CH-013 | Replace hardcoded SEO description on Home screen with a SITE_DESCRIPTION environment variable |
Low | Replaces developer-facing template copy with store-specific SEO description |
| 5 | CH-004 | Extract 5 into a named MAX_GALLERY_IMAGES constant in lib/constants |
Low | Eliminates magic number; makes gallery size configurable from a single location |
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 1 | CH-010 | Add Vitest + @testing-library/react and Playwright to the project; implement priority test cases for generateMetadata fallback logic, notFound() guards, sort resolution, and productJsonLd construction |
Very High | Establishes regression safety net before further refactoring; enables confident remediation of CH-001, CH-003, CH-008 |
| 2 | CH-009 | Verify infrastructure-level observability (Vercel, instrumentation.ts); if absent, integrate an error tracking SDK |
Medium | Makes production errors visible to the engineering team without user reports |
| 3 | CH-011 | Implement a skeleton loading state for the ProductDescription Suspense boundary |
Medium | Prevents blank right-panel on slow connections; reduces perceived load time on the most commercially important page |
| 4 | CH-014, CH-015 | Replace plain date string with <time datetime="..."> element and hardcode locale to 'en-US' on Item Detail |
Low | Improves semantic correctness, accessibility, and date format determinism |
| 5 | CH-017 | Pin exact canary version; document experimental flags; plan migration to stable Next.js 15 | Low | Reduces upgrade risk; documents intent of experimental features |
| 6 | CH-007 | Verify Prose component sanitization (see CHV-001); add server-side HTML sanitization if absent |
Medium | Closes defense-in-depth gap for merchant-authored HTML content |
| Priority | Finding ID(s) | Action | Effort | Impact |
|---|---|---|---|---|
| 1 | CH-005 | Implement cursor-based pagination on Search and Search Detail using Shopify's pageInfo.hasNextPage / endCursor API |
Very High | Prevents performance degradation as catalog grows; required for stores with >50 products per collection |
| 2 | CH-008 | Introduce typed error classes in lib/shopify to distinguish "not found" from "API error"; update page components to handle each case appropriately |
High | Improves incident detection; prevents 404 pages during Shopify API outages |
| 3 | CH-016 | Replace <span className="font-bold"> with <strong> in Search results summary |
Low | Minor semantic correctness improvement |
| Pattern | Screen(s) | Quality Dimension | Description | Recommendation |
|---|---|---|---|---|
| Consistent RSC-first architecture | All five screens | Performance Efficiency | Every assessed screen is a React Server Component with no "use client" directive, contributing zero client-side JavaScript bundle weight for page-level logic. This is a deliberate and correct application of the Next.js App Router model. |
Maintain this pattern for all new page-level components. Reserve "use client" for interactive leaf components only. |
Clean Shopify abstraction via lib/shopify |
All five screens | Maintainability | All Shopify Storefront API calls are routed through named functions in lib/shopify (getPage, getProduct, getCollection, getProducts, getCollectionProducts, getProductRecommendations). No screen directly constructs GraphQL queries or handles HTTP responses. |
Continue this pattern for any new Shopify API operations. Document the lib/shopify API surface for new developers. |
Consistent notFound() guard pattern |
Item Detail, Product Detail, Search Detail | Reliability | All three screens that fetch a single Shopify resource apply a notFound() guard immediately after the fetch, before any rendering or metadata generation proceeds. This prevents broken UI states for missing resources and is applied consistently in both generateMetadata and the page component. |
Apply this same pattern to any future screens that fetch a single resource by identifier. |
| Two-tier SEO metadata fallback chain | Item Detail, Product Detail, Search Detail | Functional Suitability | All three screens implement a consistent SEO fallback pattern: seo.title → resource.title and seo.description → resource.summary/bodySummary. This correctly models Shopify's content hierarchy and ensures SEO metadata is always populated. |
Document this pattern as the standard for all future Shopify-backed pages. |
TypeScript strict mode with noUncheckedIndexedAccess |
All five screens | Maintainability | tsconfig.json enables strict: true and noUncheckedIndexedAccess: true, providing the strongest available TypeScript type safety. This catches a class of null/undefined bugs at compile time. |
Ensure strict and noUncheckedIndexedAccess are never disabled. The unsafe type assertions in CH-003 are the exception that proves the rule — they should be fixed to restore full type safety. |
| AVIF/WebP image optimization | All five screens | Performance Efficiency | next.config.ts configures formats: ["image/avif", "image/webp"] for Next.js Image optimization, ensuring product images are served in modern, compressed formats. The remotePatterns configuration correctly scopes allowed image domains to cdn.shopify.com. |
Apply the same remotePatterns scoping discipline to any future image CDN integrations. |
| Partial Prerendering (PPR) enabled | All five screens | Performance Efficiency | next.config.ts enables ppr: true, allowing Next.js to serve a static shell instantly from the CDN while streaming dynamic content. This is a deliberate architectural choice that improves Time to First Byte for all assessed routes. |
Ensure Suspense boundaries are placed correctly to maximize the static shell size as new components are added. |
| Responsive grid with CSS-only layout | Search, Search Detail | Performance Efficiency | The grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 responsive grid is implemented entirely in CSS (Tailwind), with no JavaScript-based layout calculation. This is the correct approach for a server-rendered product grid. |
Use this same CSS grid pattern for any future product listing screens. |
HIDDEN_PRODUCT_TAG soft-visibility mechanism |
Product Detail | Security / Functional Suitability | Products tagged with HIDDEN_PRODUCT_TAG are excluded from search engine indexing via robots metadata while remaining accessible by direct URL. This is a deliberate design choice that correctly separates SEO visibility from access control, allowing draft products to be previewed without being indexed. |
Document this mechanism in the developer onboarding guide so new developers understand the distinction between "hidden from search" and "access-controlled." |
| Geist font via CSS variable | Root Layout | Performance Efficiency | The Geist font is loaded via GeistSans.variable and applied as a CSS variable on the <html> element, following Next.js's recommended font optimization pattern. This ensures the font is loaded with font-display: swap and does not block rendering. |
Apply the same CSS variable pattern for any additional fonts added to the project. |
| Screen | Route | Maintainability | Reliability | Security | Performance | Overall | Finding Count |
|---|---|---|---|---|---|---|---|
| Home | / |
Good | Good | Good | Good | Good | 2 |
| Item Detail | /[page] |
Fair | Fair | Fair | Good | Fair | 5 |
| Product Detail | /product/[handle] |
Fair | Poor | Fair | Fair | Fair | 7 |
| Search | /search |
Fair | Good | Good | Fair | Fair | 4 |
| Search Detail | /search/[collection] |
Fair | Fair | Good | Fair | Fair | 5 |
Note: Finding counts per screen reflect the number of findings where the screen is listed as an affected screen. Findings affecting all five screens (CH-009, CH-010, CH-017) are counted once per screen in this matrix.
| 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 | Software Quality Assessment based on Lifecycle Expectations — a method for classifying technical debt by type (Design, Code, Test, Doc, Dependency) and estimating remediation cost. |
| Blocker | SonarQube severity: a defect that will cause production failure or data loss. Requires immediate fix. |
| Critical | SonarQube severity: a severe code quality issue with high probability of causing bugs. Fix within current sprint. |
| Major | SonarQube severity: a significant quality issue that degrades maintainability or reliability. Fix within 30 days. |
| Minor | SonarQube severity: a code quality issue with limited impact. Fix within 90 days. |
| Info | SonarQube severity: a best practice recommendation with no immediate risk. Consider for future work. |
| Analysability | ISO 25010 sub-characteristic of Maintainability: the degree to which a system can be diagnosed for deficiencies or causes of failure. |
| Modifiability | ISO 25010 sub-characteristic of Maintainability: the degree to which a system can be modified without introducing defects. |
| Reusability | ISO 25010 sub-characteristic of Maintainability: the degree to which an asset can be used in more than one system. |
| Testability | ISO 25010 sub-characteristic of Maintainability: the degree to which test criteria can be established and tests executed. |
| Fault Tolerance | ISO 25010 sub-characteristic of Reliability: the degree to which a system operates as intended despite hardware or software faults. |
| Time Behaviour | ISO 25010 sub-characteristic of Performance Efficiency: the degree to which response and processing times meet requirements. |
| Functional Completeness | ISO 25010 sub-characteristic of Functional Suitability: the degree to which the set of functions covers all specified tasks. |
| Functional Correctness | ISO 25010 sub-characteristic of Functional Suitability: the degree to which a system provides correct results. |
| Co-existence | ISO 25010 sub-characteristic of Compatibility: the ability to perform required functions while sharing an environment with other systems. |
| Installability | ISO 25010 sub-characteristic of Portability: the degree to which a system can be installed or uninstalled in a specified environment. |
| Design Debt | Technical debt arising from architectural or design pattern issues that require structural changes to resolve. |
| Code Debt | Technical debt arising from implementation quality issues (naming, duplication, unsafe patterns) that can be resolved with targeted code changes. |
| Test Debt | Technical debt arising from missing or inadequate automated test coverage. |
| Doc Debt | Technical debt arising from missing, outdated, or incorrect documentation. |
| Dependency Debt | Technical debt arising from outdated, unstable, or risky third-party dependencies. |
| Term | Definition |
|---|---|
| Shopify Storefront API | Shopify's public-facing GraphQL API for building custom storefronts. Provides read access to products, collections, pages, and cart operations. Requires a Storefront Access Token. |
| Collection | A Shopify grouping of products, identified by a URL-safe handle string (e.g., "t-shirts"). Equivalent to a product category. |
| Handle | Shopify's URL-safe identifier for resources (products, collections, pages). Derived from the resource title (e.g., "New Arrivals" → "new-arrivals"). |
HIDDEN_PRODUCT_TAG |
A Shopify product tag (constant from lib/constants) that marks a product as non-indexable by search engines while keeping it accessible by direct URL. |
page.body |
The full HTML content of a Shopify Page, authored in Shopify's rich text editor and returned as an HTML string by the Storefront API. |
page.bodySummary |
A plain-text excerpt of a Shopify Page body, used as a fallback for SEO meta description. |
priceRange |
A Shopify object containing minVariantPrice and maxVariantPrice, each with amount and currencyCode. Represents the price spread across all product variants. |
availableForSale |
A Shopify boolean field indicating whether any variant of a product is currently purchasable. |
featuredImage |
The primary/hero image of a Shopify product, used for Open Graph tags, JSON-LD, and as the default display image. May be null for products with no images. |
sortKey |
A Shopify Storefront API enum value determining the field by which products are sorted (e.g., PRICE, TITLE, BEST_SELLING, CREATED_AT). |
defaultSort |
The fallback sort configuration from lib/constants, used when the ?sort= URL parameter is absent or unrecognized. |
sorting |
An array of sort option objects from lib/constants, each containing a URL slug, sortKey, and reverse flag. Maps user-facing sort options to Shopify API parameters. |
| Term | Definition |
|---|---|
| React Server Component (RSC) | A React component that renders exclusively on the server and ships zero JavaScript to the client. All five assessed screens are RSCs. |
| App Router | The Next.js 13+ routing system based on the app/ directory, where page.tsx files define route segments. Used throughout this application. |
generateMetadata |
A Next.js App Router export from a page file that returns a Metadata object used to populate <head> tags (title, description, Open Graph, etc.). |
notFound() |
A Next.js function from next/navigation that interrupts rendering and triggers the nearest not-found.tsx boundary, returning a 404 HTTP response. |
| Partial Prerendering (PPR) | An experimental Next.js feature (ppr: true) that serves a static HTML shell from the CDN instantly while streaming dynamic content into Suspense boundaries. |
useCache |
An experimental Next.js caching API (useCache: true in next.config.ts) for fine-grained cache control in Server Components. Subject to change in canary builds. |
React cache() |
A React 19 API that memoizes the result of a function call for the duration of a single server render pass, enabling deduplication of identical data fetches across generateMetadata and page components. |
dangerouslySetInnerHTML |
A React prop that renders a raw HTML string directly into the DOM, bypassing React's XSS protections. Requires careful sanitization of the input string. |
| JSON-LD | JavaScript Object Notation for Linked Data — a structured data format embedded in a <script type="application/ld+json"> tag that search engines use to generate rich search results. |
AggregateOffer |
A schema.org type used in JSON-LD to represent a product available at a range of prices across variants. |
| Suspense | A React primitive that shows a fallback UI while a child component's async work completes. Used on Product Detail to enable streaming HTML delivery. |
| Full Route Cache | A Next.js App Router caching layer that stores the rendered HTML and RSC payload of a route on the server/CDN, enabling fast repeated page loads. |
| Turbopack | The Rust-based Next.js bundler used in development (next dev --turbopack), replacing Webpack for faster HMR. |
noUncheckedIndexedAccess |
A TypeScript compiler option that adds undefined to the type of array index access and object property access, catching potential null-dereference bugs at compile time. |
Prose |
An internal component (components/prose) that renders a raw HTML string with typographic styling, following the Tailwind CSS prose utility class pattern. |
GridTileImage |
A reusable image tile component (components/grid/tile) used in grid and list layouts, supporting a label overlay with title and price. |
ProductGridItems |
A component (components/layout/product-grid-items) that maps over a products array and renders individual product cards in a grid layout. |
| Canary release | A pre-release, unstable build of a software package. next: "15.6.0-canary.60" is a canary build; APIs may change without semver guarantees. |