- Document viewport metadata configuration issue - Document dynamic data in cached functions issue - Outline migration strategy for Next.js 15 - List all affected files and required changes
8.4 KiB
Fix App Router Caching Issues PRD
Overview
Fix critical runtime errors in the App Router implementation related to viewport metadata configuration and dynamic data access within cached functions. These fixes are designed to work with Next.js 14 while preparing for migration to Next.js 15.
Current Issues
Issue 1: Viewport Metadata Warning
Error: "Unsupported metadata viewport is configured in metadata export. Please move it to viewport export instead."
Root Cause:
- Viewport configuration is incorrectly placed in the
metadataexport inapp/layout.tsx - Next.js 13+ App Router requires viewport to be a separate export
Impact: Warning on every page load, potential SEO and mobile rendering issues
Issue 2: Dynamic Data in Cached Functions
Error: "Route /new used 'cookies' inside a function cached with 'unstable_cache(...)'. Accessing Dynamic data sources inside a cache scope is not supported."
Root Cause:
- Data fetching functions in
app/lib/data.tsuseunstable_cachewrapper - Inside cached functions,
fetchFromApi()callsgetAuthToken()which accessescookies() - Next.js doesn't allow dynamic data sources (cookies, headers) inside cached scopes
Impact: Pages fail to load, API calls fail, poor user experience
Files to be Modified
Core Files Requiring Changes
1. /app/layout.tsx
Changes needed:
- Remove
viewportproperty frommetadataexport (line 16) - Add new
viewportexport with proper format - Fix import conflict with Radix UI's Viewport component
2. /app/lib/data.ts
Changes needed:
- Remove
unstable_cachewrapper from all 10 functions:getTeams()(lines 30-52)getTeam()(lines 58-69)getUserInfo()(lines 73-84)getRaidGroups()(lines 88-99)getVersion()(lines 103-114)getFavorites()(lines 118-129)getJobs()(lines 133-151)getJob()(lines 155-166)getJobSkills()(lines 170-182)getJobAccessories()(lines 186-197)
- Remove
unstable_cacheimport from line 1
Pages That Use These Functions (Testing Required)
3. /app/new/page.tsx
- Uses:
getRaidGroups()(line 14) - Test: Page loads, raid groups populate
4. /app/teams/page.tsx
- Uses:
getTeams() - Test: Teams list loads with filters
5. /app/saved/page.tsx
- Uses:
getFavorites() - Test: Saved teams display correctly
6. /app/p/[party]/page.tsx
- Uses:
getTeam() - Test: Individual party pages load
7. /app/[username]/page.tsx
- Uses:
getUserInfo(),getTeams() - Test: User profiles load with their teams
Supporting Files (No changes, but related)
8. /app/lib/api-utils.ts
- Contains
getAuthToken()that causes the issue - No changes needed, just awareness
Next.js 15 Migration Context
Key Next.js 15 Changes
-
Caching Behavior
- Fetch requests no longer cached by default
unstable_cachebeing deprecated in favor ofuse cachedirective- More explicit cache control
-
Async Request APIs
cookies(),headers(), andparamsbecome async- Better separation between static and dynamic data
-
Request Deduplication
- Automatic deduplication of identical requests
- No need for manual caching in many cases
Migration Strategy
Our fixes should align with Next.js 15's philosophy:
- Remove implicit caching
- Rely on framework's built-in optimizations
- Prepare for async request APIs
Proposed Solution
Solution for Viewport Issue
Move viewport configuration to a separate export in app/layout.tsx:
// Remove from metadata
export const metadata: Metadata = {
title: 'granblue.team',
description: 'Create, save, and share Granblue Fantasy party compositions',
// viewport: 'viewport-fit=cover, width=device-width, initial-scale=1.0', // REMOVE
}
// Add as separate export
export const viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
}
Solution for Caching Issue
Remove unstable_cache wrapper from all data fetching functions. This aligns with Next.js 15's default behavior and eliminates the dynamic data issue.
Before:
export async function getRaidGroups() {
const key = ['getRaidGroups'];
const run = unstable_cache(async () => {
try {
const data = await fetchFromApi('/raids/groups');
return data;
} catch (error) {
console.error('Failed to fetch raid groups', error);
throw error;
}
}, key, { revalidate: 300 });
return run();
}
After:
export async function getRaidGroups() {
try {
const data = await fetchFromApi('/raids/groups');
return data;
} catch (error) {
console.error('Failed to fetch raid groups', error);
throw error;
}
}
Rationale
- Simplicity - Removes complexity of cache management
- Next.js 15 Ready - Aligns with new caching defaults
- No Dynamic Data Issues - Cookies can be accessed freely
- Performance - Next.js automatically deduplicates requests within same render
- Maintainability - Less code to maintain and debug
Implementation Tasks
Phase 1: Immediate Fixes (Next.js 14)
File: /app/layout.tsx
- Remove viewport from metadata export (line 16)
- Add viewport as separate export
- Rename Radix Toast Viewport import to avoid naming conflict
File: /app/lib/data.ts
- Remove
import { unstable_cache } from 'next/cache'(line 1) - Remove
unstable_cachewrapper fromgetTeams()(lines 30-52) - Remove
unstable_cachewrapper fromgetTeam()(lines 58-69) - Remove
unstable_cachewrapper fromgetUserInfo()(lines 73-84) - Remove
unstable_cachewrapper fromgetRaidGroups()(lines 88-99) - Remove
unstable_cachewrapper fromgetVersion()(lines 103-114) - Remove
unstable_cachewrapper fromgetFavorites()(lines 118-129) - Remove
unstable_cachewrapper fromgetJobs()(lines 133-151) - Remove
unstable_cachewrapper fromgetJob()(lines 155-166) - Remove
unstable_cachewrapper fromgetJobSkills()(lines 170-182) - Remove
unstable_cachewrapper fromgetJobAccessories()(lines 186-197)
Testing
- Test
/newpage loads without errors (uses getRaidGroups) - Test
/teamspage functionality (uses getTeams) - Test
/savedpage functionality (uses getFavorites) - Test
/p/[party]pages load (uses getTeam) - Test
/[username]profile pages (uses getUserInfo, getTeams) - Verify API calls work with authentication
- Check console for any remaining warnings
Phase 2: Next.js 15 Migration Prep
- Document any performance impacts from removing caching
- Identify truly static data that might benefit from explicit caching
- Plan implementation of
use cachedirective for Next.js 15 - Prepare for async
cookies()andheaders()migration
Phase 3: Next.js 15 Migration
- Update to Next.js 15
- Convert
cookies()calls to async where needed - Implement
use cachedirective for static data if needed - Remove any remaining
unstable_cacheimports - Test full application functionality
Success Criteria
Immediate (Phase 1)
- No viewport metadata warnings in console
- No "cookies inside cached function" errors
- All pages load successfully
- API calls work with and without authentication
- No regression in functionality
Long-term (Phase 3)
- Successfully running on Next.js 15
- Optimal caching strategy implemented
- Clean codebase without deprecated APIs
- Performance metrics maintained or improved
Risks and Mitigations
Risk: Performance Impact
Mitigation:
- Next.js automatically deduplicates requests in same render cycle
- Monitor performance metrics before/after change
- Can implement selective caching if needed
Risk: Increased API Load
Mitigation:
- Most data fetching happens server-side with deduplication
- API should handle load or implement server-side caching
- Can add Redis/memory cache if needed
Timeline
- Phase 1: Immediate (1-2 hours)
- Phase 2: Before Next.js 15 migration (documentation only)
- Phase 3: During Next.js 15 migration sprint
Notes
- This approach prioritizes simplicity and forward compatibility
- Aligns with Next.js team's recommended patterns
- Reduces technical debt before major version upgrade
- Makes codebase more maintainable and predictable
- Total of 2 core files need modification, 5 pages need testing