Skip to content

feat: Migrate UI from Next.js to Vite + React Router#2100

Merged
benscobie merged 53 commits intomainfrom
feat-vite-react-router
Dec 1, 2025
Merged

feat: Migrate UI from Next.js to Vite + React Router#2100
benscobie merged 53 commits intomainfrom
feat-vite-react-router

Conversation

@benscobie
Copy link
Contributor

@benscobie benscobie commented Nov 28, 2025

This PR replaces Next.js with Vite + React Router 7 SPA served statically by the API. This reduced a lot of bloat, and provided a good opportunity to clean a lot of things up. Some things of note:

  • We now have URLs for "deep" pages, e.g. /rules/new, /rules/edit/2, /collections/1/exclusions. This also makes the sidebar more usable as before you could not navigate back to the overviews (fixes Menu buttons don't go back to their home pages when on a sub page  #491). The links will also stay 'active' when going to the deeper pages.
  • Page titles! Maintainerr now goes at the end, and every page should now have a good title.
  • No more janky navigations when trying to force the user onto /settings/plex to do first setup.
  • Removed most React Contexts as they weren't implemented very well and caused a few bugs. We now rely on TanStack Query caching & request de-duping (with a view of refactoring the other raw Get/Post/Put/ApiHandlers usages). I'm really liking how this acts as your primary store with the ability to do mutations and invalidations, and using hooks removes a lot of state spam.
  • Tidied up the rule group editor code; now using Zod & RHF

My reasoning for moving off of Next.js is that we did not take advantage of ANY of the features of the framework, and nor did we need to. Vite fits us a lot more I think. I probably wouldn't have done this had it not been for my excessive usage of Copilot recently. I thought I'd give it a shot on this as I've been pretty impressed. I think it did pretty well, though I wish it went a bit further with code organisation, but that can be fixed later. It saved me having to do a lot of the boring boilerplate changes so that's a nice win too.

AI Summary

Summary

  • Replace Next.js frontend with a Vite + React Router 7 SPA served statically by the Nest backend.
  • Centralize routing, bootstrapping, and data fetching via ui/src/router.tsx, ui/src/main.tsx, and new TanStack Query hooks while removing the old Next.js pages and contexts.
  • Expose PATCH /api/settings for partial updates.
  • Update tooling (Vite config, tsconfigs, ESLint, Dockerfile, turbo.json, .gitignore, .github/copilot-instructions.md) and refresh yarn.lock.

Frontend

  • Add ui/src/router.tsx with nested createBrowserRouter, error boundary, and VITE_BASE_PATH handling; delete all legacy Next artifacts.
  • Introduce ui/src/main.tsx using ReactDOM.createRoot, QueryClientProvider, and RouterProvider.
  • Implement API hook layers under ui/src/api/* and derive API_BASE_PATH from import.meta.env.
  • Remove settings context; rewrite Settings, Rules, Collections, Docs, and Plex loading pages to rely on hooks and React Router navigation.
  • Update supporting components and layout to use router primitives; modernize Vite tooling, configs, and ignores.
  • Route definitions now provide dedicated create/edit URLs (e.g. /rules/new for new rules, /rules/edit/1 for editing rule ID 1) instead of overloading list views, improving deep links and navigation.

Backend & Contracts

  • Add PlexSetupGuard, register it in the Plex API module, and extend PlexApiService with setup helpers.
  • Introduce UpdateSettingDto, PATCH /api/settings, and merge logic in the settings service; adjust rules controller/service for new UI flows.

Tooling & DevOps

  • Revise Dockerfile to build the SPA via yarn turbo build, inject VITE_BASE_PATH, and serve ui/dist from server/dist/ui.
  • Align turbo.json, workspace scripts, and .gitignore with the Vite build output; update .github/copilot-instructions.md.
  • Refresh yarn.lock for new dependencies.

Copilot AI and others added 25 commits November 27, 2025 00:01
- Install Vite, React Router, and React Helmet Async
- Create Vite configuration with base path support
- Create router configuration mapping all existing routes
- Update entry point from _app.tsx to main.tsx with RouterProvider
- Replace Next.js components:
  - Link -> react-router-dom Link
  - useRouter -> useNavigate/useLocation
  - Image -> native img tags
  - Head -> react-helmet-async Helmet
  - dynamic -> React.lazy with Suspense
- Update all component imports and navigation
- Update Layout component to use Outlet for nested routes
- Fix bowser import to use default export
- Update Dockerfile to use dist/ instead of out/
- Update turbo.json build outputs
- Remove Next.js dependencies and config files
- Update package.json scripts for Vite
- Retain BASE_PATH feature with __PATH_PREFIX__ placeholder

Co-authored-by: benscobie <[email protected]>
- Replace Next.js ESLint config with React/TypeScript config
- Add ignores for build directories (dist, .next, out)
- Install eslint-plugin-react and eslint-plugin-react-hooks
- Disable pre-existing rule violations for compatibility

Co-authored-by: benscobie <[email protected]>
- Move LibrariesContextProvider and ToastContainer from main.tsx to Layout component
- Wrap <Outlet /> with LibrariesContextProvider to match Next.js structure
- Replace all process.env.NEXT_PUBLIC_BASE_PATH with import.meta.env.VITE_BASE_PATH
- Fix ApiHandler to use import.meta.env instead of process.env

This resolves the "process is not defined" error and ensures proper context availability for all routes.

Co-authored-by: benscobie <[email protected]>
- Restructure router to make settings routes nested under /settings parent
- Add Outlet to SettingsWrapper to render child routes
- All settings sub-pages (plex, sonarr, etc.) are now children of settings route
- Settings index route redirects to /settings/main

This ensures the settings tabs remain visible when navigating between different settings pages.

Co-authored-by: benscobie <[email protected]>
- Remove NextPage type imports from all page files
- Remove all 'next' package imports
- Delete old Next.js page wrapper files in pages/ directory
- Keep only DocsPage.tsx and PlexLoadingPage.tsx which are used by router
- Remove pages/_app.tsx, pages/rules, pages/collections, pages/overview, pages/settings, etc.

All pages now use standard React function components without Next.js types.

Co-authored-by: benscobie <[email protected]>
Replaced all instances of `document.title =` with React Helmet's `<Helmet><title>` component across all pages and settings components:

**Pages Updated:**
- Overview: Maintainerr - Overview
- Rules: Maintainerr - Rules
- Collection: Maintainerr - Collections
- AddModal: Maintainerr - Overview

**Settings Pages Updated:**
- Main: Maintainerr - Settings - General
- Plex: Maintainerr - Settings - Plex
- Sonarr: Maintainerr - Settings - Sonarr
- Radarr: Maintainerr - Settings - Radarr
- Overseerr: Maintainerr - Settings - Overseerr
- Jellyseerr: Maintainerr - Settings - Jellyseerr
- Tautulli: Maintainerr - Settings - Tautulli
- Notifications: Maintainerr - Settings - Notifications
- Jobs: Maintainerr - Settings - Jobs
- Logs: Maintainerr - Settings - Logs
- About: Maintainerr - Settings - About

This provides a more React-compliant way to manage page titles and allows better integration with the existing Helmet setup in the Layout component. All titles are now managed declaratively through JSX rather than imperatively through the DOM API.

Result: Zero lint errors, build succeeds in 5.9s

Co-authored-by: benscobie <[email protected]>
- Updated .github/copilot-instructions.md to reflect Vite + React Router migration
  - Changed "Next.js frontend" to "Vite + React Router frontend"
  - Updated frontend tech stack (Vite, React Router, React Helmet, etc.)
  - Updated frontend patterns (routing, navigation, code splitting)
  - Updated UI structure to show router.tsx and main.tsx instead of pages router
  - Updated performance considerations to mention Vite HMR instead of Next.js SSG
- Removed ui/README.md (outdated Next.js documentation)

All documentation now accurately reflects the current Vite + React Router architecture.

Co-authored-by: benscobie <[email protected]>
… of state-based navigation

**Rules Routing:**
- /rules - List all rules
- /rules/new - Create a new rule
- /rules/edit/:id - Edit an existing rule

**Collections Routing:**
- /collections - List all collections
- /collections/:id - View collection (default to Media tab)
- /collections/:id/exclusions - View collection exclusions
- /collections/:id/info - View collection info

**Changes:**
- Created RulesListPage.tsx - Rules list with navigation to new/edit
- Created RuleFormPage.tsx - Unified form for creating/editing rules (uses :id param)
- Created CollectionsListPage.tsx - Collections list with navigation to details
- Created CollectionDetailPage.tsx - Collection wrapper with tabs and nested routes
- Created CollectionMediaPage.tsx - Media tab content
- Created CollectionExclusionsPage.tsx - Exclusions tab content
- Created CollectionInfoPage.tsx - Info tab content
- Updated router.tsx with nested routes structure
- Removed state-based navigation from original components

**Navigation Improvements:**
- URLs now reflect current page/tab (better for bookmarking, sharing, browser back/forward)
- Clicking a collection navigates to /collections/:id instead of showing modal
- Collection tabs use URL paths instead of state
- Rules create/edit use URL paths instead of modal state
- Browser back button works correctly with proper routing

Build succeeds in 6.2s, zero lint errors (pre-existing warnings unchanged).

Co-authored-by: benscobie <[email protected]>
**Bug Fixes:**

1. **Rule Edit Page Crash** - Fixed "can't access property toString, libraryId is undefined" error:
   - Added optional chaining in AddModal component when accessing editData.libraryId
   - Added error handling to RuleFormPage to catch API failures and redirect to rules list
   - Used editData?.libraryId instead of editData.libraryId to prevent undefined access

2. **Collection Tab Active State** - Fixed tabs not highlighting correctly when navigating:
   - Changed from using URL params (:tab) to using location.pathname to determine active tab
   - Added getCurrentTab() function that checks the full path to identify active tab
   - Media tab: /collections/:id (index route)
   - Exclusions tab: /collections/:id/exclusions
   - Info tab: /collections/:id/info
   - Now correctly highlights the active tab based on current URL

**Testing:**
- Build succeeds in 7.84s
- Zero lint errors (only pre-existing warnings remain)
- Rule edit page handles missing data gracefully
- Collection tabs now show correct active state when navigating

Both bugs are now resolved and the application works correctly.

Co-authored-by: benscobie <[email protected]>
Fixed "can't access property 'map', props.editData.rules is undefined" error by adding optional chaining to all editData property accesses:

- props.editData?.rules instead of props.editData.rules for the map operation
- Added nullish coalescing (?? true) for useRules and isActive to handle undefined
- Added optional chaining for collectionId and id properties

This ensures the rule edit page handles all cases where editData properties might be undefined, preventing crashes when loading rule data.

Build succeeds in 5.89s, zero lint errors.

Co-authored-by: benscobie <[email protected]>
@benscobie
Copy link
Contributor Author

/release-pr

@github-actions
Copy link
Contributor

Released to jorenn92/maintainerr:pr-2100 🚀

@benscobie benscobie requested a review from Copilot November 29, 2025 21:29
@benscobie
Copy link
Contributor Author

/release-pr

@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

Released to jorenn92/maintainerr:pr-2100 🚀

@benscobie
Copy link
Contributor Author

/release-pr

@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

Released to jorenn92/maintainerr:pr-2100 🚀

@benscobie
Copy link
Contributor Author

/release-pr

@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

Released to jorenn92/maintainerr:pr-2100 🚀

@benscobie
Copy link
Contributor Author

/release-pr

@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

Released to jorenn92/maintainerr:pr-2100 🚀

@benscobie benscobie marked this pull request as ready for review December 1, 2025 23:18
@benscobie benscobie requested a review from Copilot December 1, 2025 23:18
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 105 out of 108 changed files in this pull request and generated no new comments.

@benscobie benscobie merged commit a9474d4 into main Dec 1, 2025
16 checks passed
@benscobie benscobie deleted the feat-vite-react-router branch December 1, 2025 23:26
@jorenn92
Copy link
Member

jorenn92 commented Dec 6, 2025

🎉 This PR is included in version 2.23.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Menu buttons don't go back to their home pages when on a sub page

4 participants