Skip to content

feat: Migrate to Next.js 15 App Router with React 19#477

Open
colbyfayock wants to merge 35 commits into
mainfrom
nextapp
Open

feat: Migrate to Next.js 15 App Router with React 19#477
colbyfayock wants to merge 35 commits into
mainfrom
nextapp

Conversation

@colbyfayock

@colbyfayock colbyfayock commented May 5, 2023

Copy link
Copy Markdown
Owner

Summary

Complete migration from Next.js 13 Pages Router to Next.js 15.5 App Router with React 19, including package upgrades, new features, and code cleanup.

Major Changes

  • Next.js 15.5.12 with App Router (from Next.js 13.4 Pages Router)
  • React 19.2.4 (from React 18)
  • Removed Apollo Client - All GraphQL now uses native fetch() via src/lib/request.js
  • ESLint 9 with flat config (eslint.config.mjs)
  • Sass 1.97 with @use/@forward migration (no deprecation warnings)

New Features

  • /posts and /posts/page/[page] - Paginated posts archive with SSG
  • /categories - Categories index page
  • /tags - Tags index page with full tag archive support
  • /authors - Authors index page with avatars
  • /search - Client-side search results page
  • generateMetadata - Dynamic SEO metadata for all routes
  • generateStaticParams - SSG pre-rendering for posts/pages
  • Loading skeletons with shimmer animations
  • Improved error and 404 page designs

Architecture

  • Server Components by default with strategic client components
  • Template-based routing via catch-all [...uriNodes] route
  • WordPress nodeByUri queries for content resolution
  • Templates: post.js, page.js, category.js, tag.js, author.js

Package Upgrades

Package Before After
next 13.4.x 15.5.12
react 18.x 19.2.4
eslint 8.x 9.28.0
sass 1.62.x 1.97.0
fuse.js 6.x 7.x
swr 2.x 2.4.x
husky 8.x 9.x
lint-staged 13.x 16.x

Code Cleanup

  • Removed 597 lines of dead code
  • Deleted src/pages.original/ directory
  • Removed unused components: Image, Title
  • Removed unused hook: use-page-metadata.js
  • Cleaned commented code throughout

Testing Results

All routes tested and passing:

  • Homepage, posts archive, pagination ✅
  • Categories, tags, authors index ✅
  • Search, feed.xml, sitemap.xml, robots.txt ✅
  • Dynamic post/page routes ✅
  • 404 handling ✅
  • SEO: OG tags, Twitter cards, dynamic titles ✅

Breaking Changes

  • Requires Node.js 18.17+
  • Pages Router removed (App Router only)
  • Apollo Client removed (uses native fetch)

Closes previous App Router work from #397

@vercel

vercel Bot commented May 5, 2023

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
next-wordpress-starter Ready Ready Preview, Comment Feb 14, 2026 4:01am

- Remove @apollo/client and graphql dependencies
- Delete src/lib/apollo-client.js
- Migrate src/lib/menus.js to use native fetch via gql() helper
- Add graceful error handling for menu queries
- Standardize all import paths to use @/ alias
- Clean up request.js to use simple POST-only implementation
- Update various files with trailing comma fixes
- Switch from pnpm to npm (add package-lock.json)
- Update next from ^13.4.10 to ^14.2.0
- Update eslint-config-next to ^14.2.0
- Remove experimental.appDir (now stable in Next.js 14)
- Remove swcMinify (now default)
- Remove deprecated 'next export' script
- Fix env config types (POSTS_PRERENDER_COUNT and WORDPRESS_PLUGIN_SEO must be strings)
Replace full GraphQL query strings with short djb2 hashed tags
for Next.js fetch cache. Tags now include variables for proper
cache key differentiation.
- Update next from ^14.2.0 to ^15.0.0
- Update eslint-config-next to ^15.0.0
- Migrate to async params/searchParams (breaking change in Next.js 15)
- Move ImageResponse import from 'next/server' to 'next/og'
- Update react and react-dom from ^18.2.0 to ^19.0.0
- Update @types/react to ^19.0.0

Note: Next.js 16 is still in canary, staying on Next.js 15 for now
Dependencies upgraded:
- fuse.js: ^6.6.2 -> ^7.1.0
- swr: ^2.2.0 -> ^2.4.0
- sass: ^1.63.6 -> ^1.97.0
- react-icons: ^4.10.1 -> ^5.0.0
- eslint: ^8.45.0 -> ^9.0.0
- prettier: ^3.0.0 -> ^3.8.0
- husky: ^8.0.3 -> ^9.0.0
- lint-staged: ^13.2.3 -> ^16.0.0

ESLint 9 uses flat config by default; added ESLINT_USE_FLAT_CONFIG=false
to npm scripts to support legacy .eslintrc.js until migration to flat config.
Add optional chaining to prevent error when WordPress user roles
are not publicly accessible or return null.
- Add /posts archive page with pagination
- Add /posts/page/[page] for paginated posts
- Add /categories index page listing all categories
- Add /search page with client-side Fuse.js search
- Add Archive.module.scss for shared archive styling
- Fetch node data to get actual page/post title
- Use 'title' for posts/pages, 'name' for categories/users
- Return proper 'Not Found' title when node doesn't exist
- Set proper OpenGraph title and alt text
- Add getAllUsers function and QUERY_ALL_USERS query
- Pre-generate all posts, pages, categories, and author routes at build time
- Convert URIs to path segment arrays for Next.js params format
- Replace legacy .eslintrc.js with eslint.config.mjs
- Remove .eslintignore (ignores now in flat config)
- Use @eslint/eslintrc FlatCompat for compatibility with Next.js 15
- Remove eslint-plugin-prettier and eslint-plugin-react (bundled in eslint-config-next)
- Remove ESLINT_USE_FLAT_CONFIG=false workaround from scripts
- Migrate all SCSS from @import to @use/@forward syntax (Dart Sass 3.0 prep)
- Add metadataBase to layout.js for proper OG image URL resolution
- Add outputFileTracingRoot to next.config.js to prevent lockfile detection warning
- Create /authors route listing all site authors
- Display author avatar, name, and bio snippet
- Add Authors.module.scss with grid layout and hover styles
- Update QUERY_ALL_USERS to include avatar and description
- Create /tags index page listing all site tags
- Create tag archive template for individual tag pages
- Add src/data/tags.js with GraphQL query for all tags
- Add src/lib/tags.js with getAllTags() and getTags() functions
- Update catch-all route to handle Tag typename
- Add tags to generateStaticParams for SSG
- Add loading.js with skeleton UI for root and catch-all routes
- Redesign error.js with larger error code, cleaner layout, and retry button
- Redesign not-found.js with consistent styling
- Add shimmer animation for loading skeletons
- Create Loading.module.scss with reusable skeleton components
- Remove src/pages.original/ directory (legacy Pages Router reference)
- Remove unused Image and Title components
- Remove unused usePageMetadata hook
- Remove legacy constructPageMetadata and helmetSettingsFromMetadata functions
- Remove unused removeExtraSpaces utility function
- Clean up commented code from templates (author, category, page, post)
- Clean up commented code from layout.js
@colbyfayock colbyfayock changed the title App Directory feat: Migrate to Next.js 15 App Router with React 19 Feb 9, 2026
@colbyfayock colbyfayock marked this pull request as ready for review February 9, 2026 20:10
- Remove redundant site name suffix from archive page titles (posts, categories, tags, authors)
- Add Tag fragment to nodeByUri query to properly fetch tag names for metadata
- Upgrade actions to v4 (checkout, setup-node, pnpm/action-setup)
- Update pnpm to v9
- Use Node 18.x and 20.x (Next.js 15 requires Node 18.17+)
- Remove deprecated 'pnpm export' command
- Add pnpm-lock.yaml for CI caching
- Add revalidate = 3600 (1 hour) to all static pages
- Create /api/revalidate webhook endpoint for on-demand updates
- Pages now cached and served instantly
- Rebuilds automatically after 1 hour or via WordPress webhook
@hiSandog

Copy link
Copy Markdown

Nice migration — the catch-all [...uriNodes] + nodeByUri template dispatch is a clean way to map WP routes onto the App Router. A few things worth a look:

  • next.config.js computes wordpressHost/wordpressProtocol from WORDPRESS_GRAPHQL_ENDPOINT at config-load time with no guard. If that env var is unset (fresh clone, CI without secrets), .replace(...) runs on undefined and next dev/next build hard-fail with an opaque TypeError before any page renders. The old config only read the var inside the env block, so a fallback (|| '') or building remotePatterns only when the endpoint exists would restore the old robustness.
  • images.remotePatterns only allowlists the GraphQL endpoint host, but WP media is frequently served from a different domain (a CDN, or WP_HOME ≠ the GraphQL endpoint). next/image will refuse to optimize those. Worth deriving the media host from the WP site URL or making remotePatterns configurable.
  • The ISR fallback (revalidate = 3600) is reasonable — is the /api/revalidate route wired to WP publish/update/delete webhooks? If not, content edits only surface on the hourly cycle, which is a step back from on-demand updates for a CMS-backed site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants