How React SPA Devs Master Next.js 15 App Router

For React developers migrating from SPAs · Based on PedroTech Next.js 15 App Architecture Method

// TL;DR

If you've built React single-page apps with Create React App or Vite, the PedroTech Next.js 15 App Architecture Method retrains your instincts for a server-first world. Instead of client-side routing, useEffect data fetching, and manual head management, you'll use file-based routing, async server components, and exported metadata. This guide maps your existing SPA habits to Next.js 15 conventions, highlights where hooks-in-server-components will break, and shows how to keep only the truly interactive parts on the client. Use it when porting an existing React app or starting your first Next.js project.

Why does Next.js feel so different from a React SPA?

In a traditional React SPA, everything runs in the browser: routing, data fetching with useEffect, and state management all happen client-side. Next.js 15 flips this. Components are server components by default, rendering on the server and shipping as HTML. React hooks, onClick handlers, and browser APIs simply don't work until you opt into a client component with `'use client'`. This is the biggest mental shift — you're no longer writing client-first code.

How do I replace React Router with file-based routing?

Stop declaring routes in code. In Next.js, the folder name inside `/app` is the URL, and `page.tsx` inside it is the UI. A route like `/dashboard/settings` is just `app/dashboard/settings/page.tsx`. Dynamic routes use square brackets: `app/users/[userId]/page.tsx`. There's no Route component and no path prop. Use the `Link` component from `next/link` for navigation instead of anchor tags to preserve client-side transitions — plain anchors trigger full page reloads and kill performance.

How do I fetch data without useEffect?

This is the most satisfying change for SPA developers. Make your `page.tsx` function `async` and await your fetch or database call directly in the component body:

```tsx

export default async function Page({ params }: { params: Promise<{ userId: string }> }) {

const { userId } = await params;

const user = await fetch(`${process.env.NEXT_URL}/api/users/${userId}`);

if (!user) notFound();

return

{user.name}
;

}

```

No useEffect, no useState loading flag, no loading spinner logic. For the loading state, add a `loading.tsx` file in the route folder and Next.js shows it automatically while data resolves. For errors, add `error.tsx`.

Where do my interactive components go now?

Keep them, but isolate them. If a page is mostly static but has one interactive widget, keep the page as a server component and extract only the interactive part into a separate file with `'use client'` at the top. Import that client component into your server page. This pattern — server shell, client islands — keeps most of your app server-rendered and fast, while still supporting useState, useEffect, and event handlers where you genuinely need them.

What SPA habits will break my Next.js app?

Watch for these carryover mistakes: using useState or useEffect in a server component (the app breaks — add `'use client'` or extract the logic); using onClick in a server component (impossible without `'use client'`); fetching your own API route with a relative path server-side (server fetches need the absolute URL from `NEXT_URL`); and putting your components folder inside `/app` (Next.js treats it as a route). Also remember to whitelist external image hostnames in `next.config.ts` under `remotePatterns` when using the `Image` component.

How do I handle metadata I used to set with react-helmet?

Instead of manipulating the head at runtime, export a `metadata` constant from your server-component `page.tsx` or `layout.tsx` with title, description, and keywords. Root layout metadata sets global defaults; page-level metadata overrides per route. This injects tags into the HTML head declaratively and works with server rendering, so search engines and social crawlers see it immediately — a big upgrade from client-side head management.

Next step: Scaffold a fresh project with `npx create-next-app`, recreate one route from your existing SPA as an async server component, and extract its interactive piece into a `'use client'` island. Once that clicks, migrate the rest route by route.

// FREQUENTLY ASKED QUESTIONS

Can I still use useState and useEffect in Next.js 15?

Yes, but only inside client components marked with 'use client' at the top of the file. Server components — the default — cannot use them and will break. The recommended pattern is to keep pages as server components and extract interactive logic into small client components that you import, so you keep hooks only where they're truly needed.

Do I need React Router in a Next.js app?

No. Next.js provides file-based routing through the /app directory, so you don't install or configure React Router. Folder names become URL segments and page.tsx files become the UI. For navigation, use the Link component from next/link and programmatic navigation from next/navigation instead of React Router's hooks.

Will my existing fetch logic work when I move it server-side?

Mostly, but with one critical change: when fetching your own API routes from a server component, you must use an absolute URL from an environment variable like NEXT_URL, not a relative path. Relative paths only work in client components. External API URLs already absolute will work fine directly in async server components.