# TanStack Start → Next.js 15 Migration Checklist

Scaffold delivered in this folder: `app/layout.tsx`, `app/product/[slug]/*`, `app/category/[slug]/*`,
`app/sitemap.ts`, `app/robots.ts`, `lib/api.ts`, `lib/format.ts`. Everything else below is what's
still needed to have a complete, working Next.js app — this is phase 1 of the migration, not the
whole site.

## Phase 0 — Backend prerequisite (do this first, independent of the frontend rewrite)

- [ ] Add `slug`, `meta_title`, `meta_description` columns to the `products` table (see
      `SEO_AUDIT_REPORT.md` at the project root, Priority Fix #2). Until this lands, `lib/api.ts`
      falls back to matching on the product `id`, so `/product/[slug]` silently becomes
      `/product/[id]` again — not a blocker to start the migration, but do this before launch or
      the "keyword-rich slug" goal isn't actually met.
- [ ] Expose `metaTitle`/`metaDescription` on `api/products.php` and `api/categories.php` responses
      once those columns exist (`ApiProduct.metaTitle` / `ApiCategory.metaTitle` in `lib/api.ts`
      already expect them).
- [ ] Confirm `api/settings.php` → SEO tab robots.txt content actually includes
      `Disallow: /cart /checkout /admin /api` — `app/robots.ts` enforces this at the Next.js level
      regardless, but keep the two in sync.

## Phase 1 — Project setup

- [ ] `npx create-next-app@latest` (App Router, TypeScript, Tailwind, keep `src/` off — this
      scaffold assumes `app/` and `lib/` at the project root to match the files delivered here).
- [ ] Set env vars: `API_BASE_URL` (server-side PHP API origin) and `NEXT_PUBLIC_SITE_URL`
      (public site URL, used in canonical/OG tags and the sitemap).
- [ ] Copy `src/styles.css` → `app/globals.css` **unchanged**. It's already Tailwind v4's
      CSS-first config (`@theme inline`, oklch tokens, `@custom-variant dark`) — there is no
      `tailwind.config.ts` to port, and none needs to be created. Remove the `@import
      "@fontsource/..."` lines only (replaced by `next/font/google` in `app/layout.tsx`); leave
      every design token, radius, and color variable exactly as-is.
- [ ] Port `components.json` / `src/components/ui/*` (shadcn primitives) as-is — no Tailwind class
      changes needed, they're framework-agnostic React.

## Phase 2 — Port shared libs (client-side state, unchanged logic)

These are React context/hooks with no router dependency — copy over verbatim, add `"use client"`
at the top of each:
- [ ] `src/lib/cart.tsx` → `lib/cart.tsx`
- [ ] `src/lib/wishlist.tsx` → `lib/wishlist.tsx`
- [ ] `src/lib/auth.tsx` → `lib/auth.tsx` (includes the cross-tab logout sync — no changes needed,
      `BroadcastChannel`/`localStorage`/`focus` events all work identically in Next.js)
- [ ] `src/lib/reviews.tsx`, `src/lib/history.tsx`, `src/lib/settings.tsx`, `src/lib/categories.tsx`,
      `src/lib/store.tsx` (products context) → same pattern.
- [ ] Wrap the whole client-side provider tree (`AuthProvider` → `SettingsProvider` →
      `CategoriesProvider` → `ProductsProvider` → `WishlistProvider` → `ReviewsProvider` →
      `HistoryProvider` → `CartProvider`) in one `app/providers.tsx` client component, rendered
      from `app/layout.tsx`.

## Phase 3 — Port static chrome (Header/Footer/etc.)

- [ ] `src/components/site/Header.tsx`, `Footer.tsx` → mostly static, can likely stay Server
      Components; anything using `useAuth()`/`useCart()` inside them needs `"use client"`.
- [ ] `src/components/site/WhatsAppButton.tsx` + `BackToTop.tsx` → port as one client island exactly
      as already restructured (single fixed flex-col wrapper, both buttons as plain flex children —
      see the current `src/routes/__root.tsx` for the wrapper markup). No class changes needed.
- [ ] `src/components/site/CookieConsent.tsx`, `src/components/site/SeoTags.tsx` → `SeoTags.tsx`'s
      job (site verification meta tags, tracking pixels) moves into `generateMetadata()` /
      `app/layout.tsx` `<head>` where possible; anything that must stay runtime-conditional
      (analytics pixels reading a settings toggle) can stay a small client component.

## Phase 4 — Convert every other route

The two most SEO-critical pages (product, category) are done in this scaffold. Every other route
under `src/routes/*.tsx` needs the same treatment: split server-fetchable content from
`"use client"` interactive islands. Priority order:
1. `src/routes/index.tsx` (homepage) — highest-traffic page, do it right after product/category.
2. `src/routes/cart.tsx`, `checkout.tsx`, `checkout.success.tsx` — pure client state, no SEO value;
   these can be `"use client"` pages almost entirely as-is with `<Link>`/router hook swaps only.
3. `src/routes/account.tsx`, `src/routes/admin.tsx` — both already `noindex`-appropriate (add
   `robots: { index: false }` in their metadata, and confirm they're in the `robots.ts`
   `disallow` list, which they already are for `/account` and `/admin`).
4. Remaining static pages: `about`, `contact`, `faq`, `shipping`, `terms`, `privacy`, `refund`.

## Phase 5 — Mechanical find/replace across every ported file

- [ ] `import { Link } from "@tanstack/react-router"` → `import Link from "next/link"`
- [ ] `<Link to="/x">` → `<Link href="/x">`
- [ ] `<Link to="/product/$id" params={{ id }}>` → `<Link href={`/product/${slug}`}>`
- [ ] `useNavigate()` + `navigate({ to: "/x" })` → `useRouter()` (from `next/navigation`) +
      `router.push("/x")`
- [ ] `useSearch({ from: "/route" })` → `useSearchParams()` (from `next/navigation`)
- [ ] `createFileRoute(...)` + `Route.useParams()` → delete; params come from the page's own
      `params` prop instead (see `app/product/[slug]/page.tsx`)
- [ ] Every `<img src=... />` → `<Image src=... fill sizes="..." />` or fixed `width`/`height` —
      **add `priority` only on the single largest above-the-fold image per page** (hero banner on
      home, primary gallery image on product) to protect LCP; do not add `priority` everywhere,
      it defeats the purpose.

## Phase 6 — Verify zero visual regression

- [ ] Side-by-side diff every converted page against the live TanStack Start site at the same
      breakpoints (375px, 768px, 1280px) — className strings were copied verbatim in this scaffold
      specifically so nothing should move; if something shifts, it's almost always a missing
      `width`/`height`/`fill` on a converted `<Image>`, not a Tailwind issue.
- [ ] Run Lighthouse (mobile) on `/product/[slug]` and `/category/[slug]` before wiring up the rest
      of the site — confirm LCP/CLS/INP targets are actually met on these two templates before
      investing time in the remaining pages.
