Start.dev
Back to blog

Next.js after React: what actually changes

See what changes after React: which file is a page, when a component needs the browser, and why a notes list can skip useEffect.

Ariel Weinberger
Ariel Weinberger ยท 7 min read

What actually changes when you open a Next.js project after learning React, and do you still load data with useEffect?

Short answer: React still renders the UI. Next.js adds the pages, runs components on the server by default, and lets a page wait for data before it sends HTML. You still write React. The new question is where that React runs.

By the end of this post you will know which file becomes a URL, when a button has to opt into the browser, and why a notes list can skip useEffect. New to components? Start with Your First Component (opens in a new tab), then come back.

A page is still a component

In the App Router, a page is a React component with a default export. The folder is the URL, and the file has to be named page, which is page.tsx in these examples. Next.js Fundamentals (opens in a new tab) is built on that setup.

export default function Page() {
  return <h1>Notes</h1>
}

Put that in app/page.tsx and it is the home page, /. The same kind of component in app/notes/page.tsx is /notes. A folder with no page.tsx isn't a page you can visit, which is why app/notes/NoteButton.tsx can sit beside the page without becoming its own URL. You can name the function whatever you like. What matters is the default export, in a file named page.tsx.

That first change is smaller than the new files make it look. In a plain React app you usually configure a router and keep a list of paths. Here you create a folder and drop in page.tsx, and the path exists because the folder exists. The File Based Routing (opens in a new tab) lesson is the full map.

Most of the page runs on the server

In a plain React app, your components run in the browser. Here, a component is a Server Component unless you say otherwise. The server renders it to HTML and sends that HTML down, so that component's code doesn't ship to the browser at all. That is useful for a notes page that only needs to show titles. It is the wrong place for a button that updates a count, because useState, useEffect, and onClick need the browser.

Those go in a Client Component. The opt-in is a string on the first line of the file, before the imports:

"use client"
 
import { useState } from "react"
 
export default function NoteButton() {
  const [saved, setSaved] = useState(false)
  return (
    <button onClick={() => setSaved(true)}>{saved ? "Saved" : "Save"}</button>
  )
}

The server page can render that button and pass it data as props. The button can't import a Server Component and expect it to run on the server, which is why the interactive piece should stay small. Leave the directive out and Next.js throws an error as soon as that file uses useState, useEffect, or onClick. The fix is the line at the top of the button's file, not a "use client" on the whole notes page. State inside the button is the same React you already know, and useState or useReducer still applies there.

The fetch moves out of useEffect

This is the part that trips people who learned data loading in React. The URL below is a placeholder, not a live notes API.

The React version renders once with no notes, then loads them in the browser:

"use client"
 
import { useEffect, useState } from "react"
 
export default function Notes() {
  const [notes, setNotes] = useState<{ id: string; title: string }[]>([])
 
  useEffect(() => {
    fetch("https://example.com/api/notes")
      .then((res) => res.json())
      .then(setNotes)
  }, [])
 
  return (
    <ul>
      {notes.map((note) => (
        <li key={note.id}>{note.title}</li>
      ))}
    </ul>
  )
}

The page is on screen before the notes are, which is why this pattern so often needs a loading state. The useEffect guide is the plain-React version of that idea.

A Server Component can be async and wait for the data before it sends HTML:

export default async function Notes() {
  // The server waits here, so the HTML already includes the notes.
  const res = await fetch("https://example.com/api/notes")
  const notes = await res.json()
 
  return (
    <ul>
      {notes.map((note) => (
        <li key={note.id}>{note.title}</li>
      ))}
    </ul>
  )
}

Because the server waits for the data, there is no useEffect and no empty first render to fill in later. Here is the order for one visit to /notes. The server runs Notes, waits for fetch, and only then builds the HTML, with the titles already inside the list. The browser paints that HTML. There is no second trip where React fills an empty list, which is the trip the useEffect version was making, and the reason that version needs a loading state this one can skip.

The request runs on the server, so a token used in that fetch doesn't have to ship to the browser. The Data Fetching In Next.js (opens in a new tab) lesson is this comparison.

That await only works in a Server Component. If the file has "use client" because it needs a click or state, it is back to useEffect for its own fetch. Load the notes on the server and pass them down as props. The page keeps the request, and the button keeps the click.

Which kind of component is this?

Plain ReactServer ComponentClient Component
Where it runsIn the browserOn the server, by defaultIn the browser, after you opt in
useState and onClickYesNoYes
How a notes list gets its dataUsually useEffect after the first renderawait fetch before the HTML is sentuseEffect, if this file owns the fetch
When it fitsA React app you wire up yourselfPages, layouts, and dataA button, a filter, a field

Common mistakes

  • Adding useState to a file that is still a Server Component. Next.js throws an error because that hook needs the browser. Move the button into its own file and mark that file "use client".
  • Marking the whole notes page as a client component to silence the error. That ships more of the page to the browser and pulls the fetch back into useEffect. Keep the page on the server and opt in only the button.
  • Treating every file in app/ as a route. Only a page file (page.tsx here) makes a page you can visit. app/notes/NoteButton.tsx is a component. app/notes/page.tsx is the page.
  • Importing a Server Component into a client file and expecting the server to run it. The arrow goes one way. The server page renders the client button, not the other way around.

What you can leave alone

Components, props, and JSX don't change. A list still needs a key. A client button still uses useState the way you learned it in React Fundamentals. The page files use the .tsx extension. The one type annotation above, on the useState in the client notes list, tells TypeScript what a note looks like. The rest reads as the React you already know. The file tree is the new part. The component model isn't.

Quick recap

  • A page is a React component in page.tsx. The folder is the URL.
  • Components run on the server unless the file starts with "use client".
  • useState, useEffect, and onClick belong in the client file, and that file should stay small.
  • A Server Component can await fetch and send HTML that already includes the notes.
  • Props, JSX, and key still work the way you learned them.

Frequently asked questions

Is Next.js a replacement for React?

It isn't a replacement. React still renders the components, and Next.js is the framework around that, adding routing, server rendering, and a place to load data. The Next.js Fundamentals (opens in a new tab) course assumes you have finished React Fundamentals (opens in a new tab), or that you are already comfortable building React apps.

Do I still need useEffect in Next.js?

Often you don't, for data the page can load on the server. You still need it, or another client hook, when the fetch belongs to a component that has to run in the browser. The notes list in this post is the first case.

What is the App Router?

It is the routing setup where a folder inside app/ is a URL and page.tsx is the page. You don't keep a separate list of routes. That is the setup the Next.js course uses.

What is the difference between a Server Component and a Client Component?

A Server Component runs on the server and is the default. A Client Component opts into the browser with "use client" so it can use state and clicks. A server page can render a client button. A client file can't pull a Server Component back onto the server.

Do I need to know React before Next.js?

This course expects React first. You want to be comfortable building a component, passing props, and keeping state, which is what React Fundamentals (opens in a new tab) covers. Next.js assumes those pieces.

Practice

The matching exercises are in the course. Create the page in Your First Page (opens in a new tab), make the server-and-client split in Server Vs Client Components (opens in a new tab), and load data in Your First Server Fetch (opens in a new tab). Each exercise works in your browser, so there is nothing to install.

Later in that course you build a small coffee shop, Roastery, with this same split.

Keep going

Open the course that matches where you are. If components are rusty, React Fundamentals (opens in a new tab) is the right first click. Brad Traversy (opens in a new tab) teaches Next.js Fundamentals (opens in a new tab), which is where the notes-page ideas become a project you run.

Level up your coding skills

Jump into interactive courses and start building real-world projects.

Start learning

Related posts

What useEffect really does, when it runs, and how the dependency array and cleanup function work. Simple examples for fetching data, timers and page titles.
Ariel Weinberger
Ariel Weinberger ยท 5 min read
useState or useReducer? See the same cart built both ways, the signs it's time to switch, and a simple rule of thumb for choosing in your React components.
Ariel Weinberger
Ariel Weinberger ยท 5 min read
Learn how REST APIs work by following one request from client to server and back: URLs, methods, headers, status codes, JSON, and what REST actually means.
Ariel Weinberger
Ariel Weinberger ยท 7 min read