diff --git a/skills/frontend/vite-to-nextjs-migration-skill/SKILL.md b/skills/frontend/vite-to-nextjs-migration-skill/SKILL.md
new file mode 100644
index 0000000..75384d3
--- /dev/null
+++ b/skills/frontend/vite-to-nextjs-migration-skill/SKILL.md
@@ -0,0 +1,421 @@
+---
+name: vite-to-nextjs-migration
+description: |
+ Migrating legacy PolicyEngine tools from Vite + react-router-dom to Next.js 16 App Router.
+ Use when updating older repos to match current PolicyEngine frontend standards.
+ Triggers: "migrate from Vite", "convert to Next.js", "update to App Router",
+ "remove react-router-dom", "Vite to Next.js"
+---
+
+# Migrating from Vite to Next.js App Router
+
+How to migrate legacy PolicyEngine React apps from Vite + react-router-dom to Next.js 16 App Router with Tailwind 4.
+
+## Background
+
+Older PolicyEngine tools (pre-2024) may use Vite as their bundler with react-router-dom for routing. Current PolicyEngine standards require Next.js 14+ App Router for all frontend applications. This guide covers the migration pattern.
+
+## Migration Checklist
+
+### 1. Update dependencies
+
+**Remove:**
+```json
+{
+ "vite": "*",
+ "@vitejs/plugin-react": "*",
+ "react-router-dom": "*"
+}
+```
+
+**Add:**
+```bash
+bun add next@16.2.6 react@19.2.0 react-dom@19.2.0
+bun add -D @tailwindcss/postcss postcss
+bun add @policyengine/ui-kit
+```
+
+### 2. Replace Vite config with Next.js config
+
+**Delete:**
+- `vite.config.ts` or `vite.config.js`
+- `index.html` (Vite's HTML entry point)
+
+**Create `next.config.ts`:**
+```typescript
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ output: "export", // for static export if needed
+ images: {
+ unoptimized: true, // required for static export
+ },
+};
+
+export default nextConfig;
+```
+
+### 3. Restructure to App Router filesystem
+
+**Before (Vite):**
+```
+src/
+ App.tsx ← router setup with react-router-dom
+ pages/
+ Home.tsx
+ Country.tsx
+ main.tsx ← ReactDOM.render()
+index.html
+```
+
+**After (Next.js App Router):**
+```
+app/
+ layout.tsx ← root layout with wrapper
+ page.tsx ← homepage (redirects if needed)
+ [countryId]/
+ page.tsx ← dynamic route for country pages
+ globals.css ← Tailwind imports
+```
+
+### 4. Convert routing patterns
+
+#### React Router → Next.js App Router
+
+**Before (react-router-dom):**
+```tsx
+// App.tsx
+import { BrowserRouter, Routes, Route } from "react-router-dom";
+
+function App() {
+ return (
+
+
+ } />
+ } />
+
+
+ );
+}
+```
+
+**After (Next.js filesystem):**
+```tsx
+// app/page.tsx - homepage
+import { redirect } from "next/navigation";
+
+export default function RootPage() {
+ // Redirect based on locale or default
+ redirect("/us");
+}
+
+// app/[countryId]/page.tsx - country page
+export default function CountryPage({
+ params,
+}: {
+ params: { countryId: string };
+}) {
+ const { countryId } = params;
+ return ;
+}
+```
+
+#### Dynamic imports and navigation
+
+**Before:**
+```tsx
+import { useNavigate, useParams } from "react-router-dom";
+
+function Component() {
+ const navigate = useNavigate();
+ const { countryId } = useParams();
+
+ return ;
+}
+```
+
+**After:**
+```tsx
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+
+function Component({ params }: { params: { countryId: string } }) {
+ const router = useRouter();
+ const { countryId } = params;
+
+ // Prefer Link for navigation
+ return Go to UK;
+
+ // Or router.push for programmatic navigation
+ // router.push("/uk");
+}
+```
+
+### 5. Set up Tailwind 4 + ui-kit
+
+**Create `postcss.config.mjs`:**
+```js
+export default {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+```
+
+**Create `app/globals.css`:**
+```css
+@import "tailwindcss";
+@import "@policyengine/ui-kit/theme.css";
+```
+
+**Import in `app/layout.tsx`:**
+```tsx
+import "./globals.css";
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
{children}
+
+ );
+}
+```
+
+**Delete:**
+- `tailwind.config.js` or `tailwind.config.ts` (Tailwind v4 uses CSS config)
+- Old CSS import from `main.tsx` or `index.html`
+
+See `policyengine-ui-kit-consumer-skill` for full Tailwind 4 setup details.
+
+### 6. Convert data imports
+
+Vite's special import features (e.g., `?raw`, `?url`) don't exist in Next.js.
+
+**Before (Vite raw import for YAML):**
+```tsx
+import dataYaml from "./data.yaml?raw";
+import yaml from "js-yaml";
+
+const data = yaml.load(dataYaml);
+```
+
+**After (Next.js - use JSON instead):**
+```tsx
+import data from "./data.json";
+// Just use the data directly
+```
+
+**Migration:** Convert YAML files to JSON:
+```bash
+# Manual conversion or use a script
+python -c "import yaml, json, sys; json.dump(yaml.safe_load(sys.stdin), sys.stdout, indent=2)" < data.yaml > data.json
+```
+
+Or for multiple files:
+```python
+# scripts/convert_yaml_to_json.py
+import yaml
+import json
+from pathlib import Path
+
+for yaml_file in Path("src/data").glob("*.yaml"):
+ with open(yaml_file) as f:
+ data = yaml.safe_load(f)
+
+ json_file = yaml_file.with_suffix(".json")
+ with open(json_file, "w") as f:
+ json.dump(data, f, indent=2)
+```
+
+### 7. Update package.json scripts
+
+**Before:**
+```json
+{
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ }
+}
+```
+
+**After:**
+```json
+{
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start"
+ }
+}
+```
+
+### 8. Handle environment variables
+
+**Before (Vite):**
+- Variables prefixed with `VITE_` are exposed to client
+- Access via `import.meta.env.VITE_API_URL`
+
+**After (Next.js):**
+- Variables prefixed with `NEXT_PUBLIC_` are exposed to client
+- Access via `process.env.NEXT_PUBLIC_API_URL`
+
+**Migration:**
+1. Rename env vars in `.env`:
+ ```diff
+ - VITE_API_URL=https://api.example.com
+ + NEXT_PUBLIC_API_URL=https://api.example.com
+ ```
+
+2. Update references in code:
+ ```diff
+ - const apiUrl = import.meta.env.VITE_API_URL;
+ + const apiUrl = process.env.NEXT_PUBLIC_API_URL;
+ ```
+
+### 9. Root layout requirements
+
+Next.js App Router requires a root `app/layout.tsx` with `` and `` tags:
+
+```tsx
+import type { Metadata } from "next";
+import "./globals.css";
+
+export const metadata: Metadata = {
+ title: "PolicyEngine Tool Name",
+ description: "Tool description for SEO",
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+```
+
+Do NOT include `` or `` in individual page components.
+
+### 10. Client vs Server Components
+
+Next.js App Router uses React Server Components by default. Components that use:
+- `useState`, `useEffect`, `useContext`
+- Event handlers (`onClick`, `onChange`, etc.)
+- Browser APIs (`window`, `localStorage`, etc.)
+
+Must be marked as client components:
+
+```tsx
+"use client";
+
+import { useState } from "react";
+
+export default function InteractiveComponent() {
+ const [count, setCount] = useState(0);
+ return ;
+}
+```
+
+**When in doubt:** Add `"use client"` at the top of the file. Server Components are an optimization, not a requirement for migration.
+
+## Common Migration Gotchas
+
+### 1. Missing `"use client"` directive
+
+**Error:**
+```
+You're importing a component that needs useState. It only works in a Client Component
+but none of its parents are marked with "use client"
+```
+
+**Fix:** Add `"use client"` to the top of the component file.
+
+### 2. Hydration mismatches
+
+**Error:**
+```
+Hydration failed because the initial UI does not match what was rendered on the server
+```
+
+**Common causes:**
+- Using `Date.now()` or `Math.random()` directly in render
+- Accessing `window` or `localStorage` without checking if it exists
+- Different content between server and client
+
+**Fix:** Move client-only logic into `useEffect`:
+```tsx
+"use client";
+import { useEffect, useState } from "react";
+
+export default function Component() {
+ const [clientData, setClientData] = useState(null);
+
+ useEffect(() => {
+ setClientData(window.localStorage.getItem("key"));
+ }, []);
+
+ return
{clientData}
;
+}
+```
+
+### 3. Import.meta.url not available
+
+**Error:**
+```
+Cannot use 'import.meta' outside a module
+```
+
+**Fix:** Next.js doesn't support `import.meta` in the same way Vite does. For file paths, use `path` module in Node.js context or public directory for static assets.
+
+### 4. Index.html removal
+
+Vite uses `index.html` as the entry point. Next.js generates HTML from `app/layout.tsx`.
+
+**Migration:**
+- Move any `` tags to `metadata` export in layout.tsx or use `next/head`
+- Move `