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:

  1. βœ… URL was correct: http://localhost:3000/cards/ac914d98-221e-426c-8a50-342896b15f9e?art=b8d53b6d-edce-47eb-84a9-528cd60f18e2
  2. ❌ Gallery showed wrong art: displaying the default print instead of the requested variant
  3. ❌ "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 useEffect approaches with dependency arrays
  • useRef tracking 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:

  1. Static for base URLs (/cards/{oracle})

    • Pre-rendered at build time
    • Fast CDN caching
    • Great for SEO
    • Perfect for default card views
  2. Dynamic for parameterized URLs (/cards/{oracle}?art={id})

    • Rendered on-demand when requested
    • Reads searchParams at 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 params
  • force-dynamic - Always server-rendered
  • auto - Smart selection based on features used
  • error - 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 selectedArtId value
  • βœ… Multiple art variants can be added to cart and navigated correctly

Impact

This fix enables the complete cart workflow:

  1. Browse cards - Find a card you like
  2. Add specific print - Choose exact art variant and finish
  3. Build cart - Add multiple cards across different pages
  4. Click to review - Click cart item to see full details
  5. See correct art - Gallery shows the exact printing you added
  6. Add to cart again - Add more copies of the same printing
  7. Round-trip works - Cart β†’ Detail β†’ Cart maintains data integrity

Code References

Changed Files

  1. app/cards/[oracle]/page.tsx

    • Changed dynamic = 'force-static' to dynamic = 'auto'
    • Removed alternates.canonical from metadata
    • Added debug logging for searchParams
  2. components/CardPrintGallery.tsx

    • Simplified state initialization (removed complex useRef tracking)
    • Streamlined useEffect for initialFeaturedId synchronization
    • Added debug logging for received props
  3. 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:

  1. Client-side URL reading - Use useSearchParams() in client component

    • ❌ Loses SSR benefits
    • ❌ Slower initial render
    • βœ… Simpler server config
  2. Separate route - /cards/{oracle}/{print_id}

    • βœ… More semantic URLs
    • ❌ Changes existing URL structure
    • ❌ More complex routing logic
  3. 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:

Related Posts