- Remove legacyBehavior prop and nested <a> tags from Link components
- Modernize to Next.js 13+ Link API with className directly on Link
- Convert external links to plain <a> tags (LinkItem, Discord link)
- Remove unnecessary passHref props from Header components
- Fix tab switching by mapping string values to GridType enum
The tab switching issue was caused by trying to parse string values
("characters", "weapons", "summons") as integers when they needed to
be mapped to GridType enum values.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
9.5 KiB
9.5 KiB
PRD: Migrate Web App from Next.js to SvelteKit (Svelte 5 + Runes)
Context
The app is a small but interactive site for building, saving, and sharing Granblue Fantasy team compositions. It currently runs on Next.js 14 using a hybrid of App Router and legacy Pages, with next-intl for i18n, Valtio for state, Radix-based UI wrappers, and Tiptap for rich text. The backend is a Rails API and will not change.
Problem / Opportunity
- The UI layer carries Next-specific complexity (hybrid routing, middleware composition, Valtio, React wrappers around primitives) for a scope that aligns well with SvelteKit’s simpler mental model.
- Svelte 5 (Runes) and SvelteKit provide small, fast bundles and ergonomic state management for highly interactive UIs like the party editor.
- A rewrite would impose upfront cost but could yield better performance, maintainability, and developer experience.
Goals
- Achieve feature and URL parity (including localized routing and auth/method guards) with the current Next.js app.
- Reuse domain types, data contracts, and i18n content; minimize churn to the Rails API layer.
- Maintain or improve perceived performance, accessibility, and UX of primitives (menus, dialogs, tooltips, toasts).
Non‑Goals
- No changes to the Rails API endpoints, auth semantics, or data shape.
- No new product features beyond parity (performance and a11y improvements are in scope).
Current State (Summary)
- Framework: Next.js 14 with
app/andpages/side by side. - Routing/i18n:
app/[locale]/…withnext-intland “as-needed” default-locale prefixing; middleware composes i18n with auth/method guards. - State: Valtio global stores for
accountStateandappState(party/grid/editor flows). - Data: axios-based
utils/api.tsx, app route handlers underapp/api/**proxy to Rails; server helpers inapp/lib/*with zod validation. - UI: Components under
components/**using SCSS modules, Radix-based wrappers, custom editor based on Tiptap React + custom extensions. - URL State:
nuqsbinds filters to query params. - Storybook: React/Next preset.
Feature/Parity Requirements
- Public routes:
/,/new,/teams,/p/[party](+ tab segments),/[username],/about,/updates,/roadmapwith locale variants under/jaand default-locale “as-needed” behavior. - Auth/method gates: protect
/savedand/profile; 401 JSON for unauthorized API mutations; preserve method-based rules under/api/**. - Party editor: weapons/summons/characters, job/skills/accessory, guidebooks, toggles, remix/delete, editability rules by cookie/user.
- Search & filters: team discovery with element/raid/recency, advanced filters from cookie, pagination (append/replace).
- i18n: reuse JSON namespaces; preserve current translation keys and locale cookie behavior.
- Primitives: dropdown menu, dialog, tooltip, toast, popover, select, switch, slider, command, segmented control with keyboard a11y.
- Editor: Tiptap with mentions, link, lists, headings, youtube, highlight, placeholder.
Proposed Direction
Rebuild the web app on SvelteKit 2 and Svelte 5 Runes. Keep the Rails API contract intact. Adopt Svelte stores for global state and Runes for local component state. Replace React-specific libraries with Svelte-native equivalents while reusing JSON messages, type declarations, API contracts, and Tiptap core extensions.
Target Architecture (SvelteKit)
- Routing:
src/routes/[lang=locale]/…to mirror localized segments; top-level/redirects to/new. - Hooks:
hooks.server.tshandles locale detection/redirects and reproduces auth/method gates;handleFetchattaches bearer tokens from cookies. - Data loading:
+layout.server.ts/+page.server.tsfor SSR (e.g., version, lists);+page.tsfor client/SSR-unified fetches. - Endpoints:
src/routes/api/**/+server.tsproxy to Rails with zod validation and consistent status codes. - State: Svelte
writablestores foraccountandapp(party, grid, search, jobs, skills, version), with typed helper actions; local UI state via runes. - Theming: small store plus SSR-safe initial theme; apply class/data-theme on
<html>. - i18n:
svelte-i18n(recommended) loading existing JSON bundles; locale cookie + accept-language fallback; “as-needed” default-locale paths.
Dependency Mapping (Equivalents)
- Framework/Core:
next→@sveltejs/kitreact,react-dom→svelte@5@svgr/webpack→vite-svg-loader(or inline SVG)
- Routing/URL State:
next/navigation→ SvelteKitgoto,$page,afterNavigatenuqs→$page.url.searchParams+ small helper orsveltekit-search-params
- Data/Query:
@tanstack/react-query→@tanstack/svelte-query@5axios→ keep or migrate tofetch; keep zod
- i18n:
next-intl→svelte-i18n(reuse JSON); removenext-i18nexti18next*libs → only if choosing an i18next-based Svelte binding (otherwise remove)
- UI/A11y:
@radix-ui/*→bits-uiprimitives and/orshadcn-sveltetippy.js→svelte-tippy(or small custom)cmdk→cmdk-svelte/cmdk-sv
- Theming:
next-themes→ small Svelte store + cookie bootstrap
- Editor:
@tiptap/react→ Tiptap core with Svelte integration (e.g.,svelte-tiptap); reuse custom extensions
- Typeahead/Select/Infinite:
react-bootstrap-typeahead→svelte-selectreact-infinite-scroll-component→svelte-infinite-loadingor IO-based action
- Misc:
cookies-next→event.cookiesserver-side;js-cookieclientreact-use/usehooks-ts→svelte-useremixicon-react→svelte-remixiconorunplugin-icons(ri:*)react-linkify→linkifyjs/linkify-html- SCSS →
svelte-preprocesswith component-scoped styles and global imports - Storybook:
@storybook/nextjs→@storybook/sveltekit
Reuse vs. Rewrite
- Reuse
- i18n bundles under
public/locales/{en,ja} - Domain types
types/*.d.ts(move tosrc/lib/types) - Pure utilities in
utils/*(parsers, enums, formatters) - API contracts and zod schemas (
app/lib/api-utils.ts) - Tiptap custom extensions (
extensions/CustomMention,CustomSuggestion) - Public assets and SVGs
- i18n bundles under
- Rewrite
- All React components and Valtio stores as Svelte components/stores
- Next middleware as
hooks.server.ts(auth + locale); API handlers as+server.ts - URL/query state helpers (replace
nuqs) - Theming (replace
next-themes) - Storybook stories
- Font handling (move
pages/fonts/gk-variable.woff2tostatic/and add@font-face)
Routing & i18n Details
- Localized route group
[lang=locale]constrained toen|ja(from existingi18n.config.ts). - “As-needed” default-locale behavior via redirects in
handleand locale-aware links. - Preserve existing route structure and query semantics (e.g.,
/teams?element=…&raid=…).
Auth & Cookies
- Mirror
middleware.tslogic: protect/savedand/profile; method-guard mixed routes; return 401 JSON for API. - Server: use
event.cookiesand setlocals.user; client:js-cookiewhere necessary. - Use
handleFetchto injectAuthorizationheader fromaccountcookie token.
State & Data
- Global state: writable stores mirroring
initialAccountStateandinitialAppStatewith typed actions; local UI state via runes. - Queries:
@tanstack/svelte-queryfor client/SSR-safe caching; SSR data via+page.server.tswhere beneficial. - Invalidation: use SvelteKit
invalidate/invalidateAllin place of NextrevalidatePath.
UI & Editor
- Primitives: build on Bits-UI/shadcn-svelte; verify keyboard navigation and focus management; carry over SCSS look-and-feel.
- Editor: use Tiptap core with Svelte wrapper; reuse mention/suggestion logic and toolbar feature set (bold/italic/strike/heading/list/link/youtube/highlight/placeholder).
Performance & SEO
- Expect smaller bundles and faster hydration from Svelte runtime.
- Maintain SSR for primary pages and content; keep linkable, crawlable party pages and listings.
- Optimize images/SVGs via Vite pipeline; keep existing public assets.
Tradeoffs (Pros / Cons)
- Pros: performance, simpler state and data flows, clearer SSR/CSR boundaries, component‑scoped styling, fast DX.
- Cons: full component rewrite, parity tuning for UI primitives, community-maintained editor bindings, team ramp‑up, Storybook conversion.
Alternatives Considered
- Stay on Next.js and complete the ongoing App Router + next‑intl consolidation (lowest risk and cost).
- Prototype SvelteKit for the party editor only (dual‑stack) and evaluate before committing.
Open Questions
- Keep proxy endpoints under
/api/**or talk to Rails directly with CORS? (Proxy simplifies auth and error shaping.) - UI library choice finalization: Bits‑UI vs shadcn‑svelte (or a minimal custom layer where needed).
- Icon strategy:
unplugin-iconsvs direct SVG imports.
Acceptance Criteria
- Route and locale parity with auth/method guards and “as‑needed” default‑locale behavior.
- Party editor parity (create/update/remix/delete; grids; jobs/skills/accessories; guidebooks; toggles).
- Query‑linked filters and pagination on
/teams,/saved,/[username]. - Editor parity (mentions, link, lists, headings, youtube, highlight, placeholder) and equivalent styling.
- Primitives parity with keyboard a11y and visual consistency.
- i18n keys resolve from existing JSON bundles; theme persists SSR + client.
Success Measures
- Time‑to‑interactive and bundle size improved vs. Next build.
- No regression in error rates for API interactions (remix/delete/save).
- Positive qualitative feedback on responsiveness in editor/grid interactions.