Fixing Art Variant Navigation: The Static vs Dynamic Rendering Challenge
Sun Oct 05 2025 19:00:00 GMT-0500 (Central Daylight Time) | 6 min read
Fixing Art Variant Navigation: The Static vs Dynamic Rendering Challenge
When building the clickable cart feature, we hit a subtle but critical bug: clicking a cart item would navigate to the correct card page, but always show the default art variant instead of the specific printing that was added to the cart. The URL was correct (/cards/{oracle}?art={print_id}), but the wrong art displayed.
This bug taught us an important lesson about Next.js rendering strategies and how they interact with query parameters.
The Problem
Users could add specific card printings to their cart (like a particular Dominaria United version of a card), but when clicking the cart item to view details:
- β
URL was correct:
http://localhost:3000/cards/ac914d98-221e-426c-8a50-342896b15f9e?art=b8d53b6d-edce-47eb-84a9-528cd60f18e2 - β Gallery showed wrong art: displaying the default print instead of the requested variant
- β "Add to Cart" button used wrong print data
The cart was building the correct URL with ?art={print_id}, the browser was navigating to it, but the server wasn't reading the parameter.
The Investigation
Step 1: Client-Side State Management
Initial hypothesis: React state synchronization issue in CardPrintGallery. We tried:
- Multiple
useEffectapproaches with dependency arrays useReftracking for previous values- Simplified state initialization logic
Result: Gallery correctly displayed whatever initialFeaturedId it received, but was receiving the wrong ID from the parent.
Step 2: Server Component Data Flow
Added debug logging throughout the stack:
// Cart sidebar
console.log('[Cart Item Link]', { oracle_id, art_id, url });
// Server component
console.log('[CardDetailPage] Params:', { oracle, selectedArtId });
// Client gallery
console.log('[CardPrintGallery] Render with:', { initialFeaturedId });
Discovery: The server logs showed selectedArtId: undefined even though the URL clearly had ?art=b8d53b6d...!
Step 3: The Root Cause
Found the culprit in app/cards/[oracle]/page.tsx:
export const dynamic = 'force-static';
export const revalidate = 3600;
The issue: force-static tells Next.js to pre-render the page at build time and serve the same HTML to all requests. Query parameters like ?art= are runtime values that don't exist at build time, so they're completely ignored!
The Fix
Changed the rendering strategy from force-static to auto:
// Before: Force static rendering (ignores query params)
export const dynamic = 'force-static';
// After: Automatic strategy selection
export const dynamic = 'auto';
export const revalidate = 3600;
What dynamic = 'auto' Does
Next.js now intelligently chooses the rendering strategy:
-
Static for base URLs (
/cards/{oracle})- Pre-rendered at build time
- Fast CDN caching
- Great for SEO
- Perfect for default card views
-
Dynamic for parameterized URLs (
/cards/{oracle}?art={id})- Rendered on-demand when requested
- Reads
searchParamsat runtime - Returns correct art variant
- Slower but necessary for dynamic content
Why This Bug Was So Tricky
Silent Failure
No errors thrown - the page just ignored the parameter and used defaults. TypeScript showed art?: string (optional), so undefined was "valid."
Worked in Development Sometimes
Development mode can behave differently than production builds, making the issue intermittent.
Correct URL, Wrong Content
The browser showed the right URL, making it look like a client-side state problem when it was actually server-side configuration.
Hidden in Configuration
The root cause was in an export const at the top of the file, not in the component logic we were debugging.
Related Issues Fixed
Canonical URL Redirect
Also discovered and fixed a related issue - the metadata generation was setting a canonical URL without the query parameter:
// Before: Caused redirect from ?art= URL to base URL
alternates: {
canonical: baseUrl // Strips ?art= parameter
}
// After: Allow art variants to be their own URLs
// No canonical URL - each art variant is valid
This was causing the browser to redirect from /cards/{oracle}?art={id} to /cards/{oracle}, losing the art parameter entirely.
Lessons Learned
1. Know Your Rendering Strategy
Next.js 14+ has several rendering modes:
force-static- Build time, no runtime paramsforce-dynamic- Always server-renderedauto- Smart selection based on features usederror- Throw error if dynamic features detected
Choose based on your needs. For pages that need query parameters, avoid force-static.
2. Debug the Whole Stack
When client state looks wrong, check if the server is sending correct data. Don't assume the issue is where you first see symptoms.
3. Console Logging is Your Friend
Strategic console.log statements at each layer of the stack revealed the exact point where data went wrong:
[Cart] Building URL: /cards/{oracle}?art={id} β
[Browser] Navigating to URL β
[Server] Reading searchParams.art: undefined β
4. Configuration Over Code
Sometimes the bug isn't in your component logic - it's in how you've configured the framework. Check export const declarations, config files, and framework-specific settings.
5. Query Parameters Need Dynamic Rendering
If your page uses searchParams, you generally can't use force-static unless you're okay with parameters being ignored.
Testing Checklist
After this fix, we verified:
- β
Clicking cart items navigates to correct URL with
?art=parameter - β URL persists in address bar (no redirect)
- β Gallery displays the exact art variant from the URL
- β "Add to Cart" button uses the featured print's data
- β Clicking different prints in gallery updates the featured card
- β
Base URLs (
/cards/{oracle}) still load fast via static rendering - β
Server console shows correct
selectedArtIdvalue - β Multiple art variants can be added to cart and navigated correctly
Impact
This fix enables the complete cart workflow:
- Browse cards - Find a card you like
- Add specific print - Choose exact art variant and finish
- Build cart - Add multiple cards across different pages
- Click to review - Click cart item to see full details
- See correct art - Gallery shows the exact printing you added
- Add to cart again - Add more copies of the same printing
- Round-trip works - Cart β Detail β Cart maintains data integrity
Code References
Changed Files
-
app/cards/[oracle]/page.tsx
- Changed
dynamic = 'force-static'todynamic = 'auto' - Removed
alternates.canonicalfrom metadata - Added debug logging for
searchParams
- Changed
-
components/CardPrintGallery.tsx
- Simplified state initialization (removed complex
useReftracking) - Streamlined
useEffectforinitialFeaturedIdsynchronization - Added debug logging for received props
- Simplified state initialization (removed complex
-
components/ScanCartSidebar.tsx
- Added debug logging for cart item URLs
Key Patterns
Server Component (reads query params):
export const dynamic = 'auto'; // Allow dynamic rendering
export default async function Page({
params,
searchParams
}: {
params: { oracle: string },
searchParams: { art?: string }
}) {
const selectedArtId = searchParams?.art; // Now actually populated!
// Use selectedArtId to select correct card variant
}
Client Gallery (respects initial prop):
const validInitialId = prints.some(p => p.id === initialFeaturedId)
? initialFeaturedId
: prints[0]?.id;
const [featuredId, setFeaturedId] = useState(validInitialId);
useEffect(() => {
if (initialFeaturedId && prints.some(p => p.id === initialFeaturedId)) {
setFeaturedId(initialFeaturedId);
}
}, [initialFeaturedId, prints]);
Future Considerations
Performance Monitoring
With dynamic = 'auto', URLs with ?art= are server-rendered on demand. Monitor:
- Response times for parameterized requests
- Cache hit rates
- Server load during peak traffic
Alternative Approaches
Other solutions we considered:
-
Client-side URL reading - Use
useSearchParams()in client component- β Loses SSR benefits
- β Slower initial render
- β Simpler server config
-
Separate route -
/cards/{oracle}/{print_id}- β More semantic URLs
- β Changes existing URL structure
- β More complex routing logic
-
Static generation with ISR - Pre-render common variants
- β Fast for popular cards
- β Complex build logic
- β Can't predict all variants
We chose dynamic = 'auto' because it:
- Maintains existing URL structure
- Preserves SSR benefits
- Requires minimal code changes
- Lets Next.js optimize automatically
Conclusion
The cart-to-detail navigation with art variants now works perfectly. Users can browse, add specific printings to their cart, and navigate back to see full details of the exact card they added.
The key lesson: when using Next.js with query parameters, ensure your rendering strategy supports runtime parameter reading. force-static is great for performance but incompatible with dynamic URLs.
This bug also reinforced the importance of:
- Understanding your framework's rendering modes
- Debugging the entire request lifecycle, not just client code
- Strategic console logging to pinpoint where data transforms
- Checking configuration before diving deep into component logic
The fix was just two words ('auto' instead of 'force-static'), but finding it required understanding the entire stack from cart click to server render to client hydration.
Related Posts: