Back to Blog
web devopsDay 1

Day 1: Next.js 15 Setup & Project Structure

January 20, 20266 min read

I came into today knowing HTML, CSS, and enough JavaScript to make things move on a page. The kind of JS where you getElementById, attach a click handler, and call it a day. Today I jumped into the deep end of modern web dev — React, Next.js, TypeScript, Tailwind — and honestly, it's a different world.

Here's everything I learned, broken down the way I wish someone explained it to me.

From Vanilla JS to React — The Mental Shift

In traditional web dev, you write HTML for structure, CSS for styling, and JavaScript to reach into the page and change things:

let count = 0;
document.getElementById('btn').addEventListener('click', function() {
  count++;
  document.getElementById('counter').textContent = count;
});

You manage two things separately: the data (count) and the DOM (the actual page elements). Every time data changes, you manually find the right element and update it.

React flips this entirely. You just describe what the page should look like for any given data, and React handles the updating:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

No getElementById. No manual DOM updates. You change the data, React figures out what needs to change on screen. This is the shift from imperative (step-by-step instructions) to declarative (describe the end result).

Components — Breaking Pages Into Pieces

In traditional HTML, you have one big page. In React, you break it into self-contained, reusable pieces called components:

Traditional:          React:
One big HTML file →   <Header />
                      <Hero />
                      <BlogList>
                        <BlogCard />
                        <BlogCard />
                      </BlogList>
                      <Footer />

Each component manages its own markup, styles, and behavior. Build a <BlogCard /> once, use it 100 times with different data. No copy-pasting HTML.

JSX — HTML Inside JavaScript

That HTML-looking code inside React? That's JSX — JavaScript that looks like HTML but compiles down to function calls. Key differences from real HTML:

  • class becomes className (class is a reserved word in JS)
  • onclick becomes onClick (camelCase)
  • Curly braces {variable} inject JavaScript expressions
  • All tags must close: <img /> not <img>

Next.js App Router — Folders Are Routes

Next.js takes React and adds the framework layer: routing, server rendering, optimization. The App Router is the key concept — your folder structure literally becomes your URL structure:

app/
├── page.tsx          → yoursite.com/
├── about/page.tsx    → yoursite.com/about
├── blog/
│   ├── page.tsx      → yoursite.com/blog
│   └── [slug]/
│       └── page.tsx  → yoursite.com/blog/any-post-here
  • page.tsx makes a folder routable
  • layout.tsx wraps pages (header, footer persist across navigation)
  • [slug] captures dynamic URL segments

No router configuration. No route files. Just folders and files.

Server Components vs Client Components

This is the biggest paradigm shift in modern Next.js. Every component is a Server Component by default:

Server Components run on the server at build time. They can read files, query databases, and fetch APIs directly. They send zero JavaScript to the browser. Most of your app should be these.

Client Components run in the browser. You opt in by adding "use client" at the top of the file. Use these only when you need interactivity — useState, onClick, useEffect, browser APIs.

The rule is simple: start with Server Components, add "use client" only when the component needs to respond to user interaction.

State, Props, and How Components Talk

Props flow data downward from parent to child — like function arguments. The parent decides what data, the child decides how to display it.

State lives inside a component and changes over time. When state updates, React re-renders the component. useState is the basic hook for this.

Hooks are functions starting with use that give components abilities:

  • useState — remember and update values
  • useEffect — run code after rendering (fetch data, set up listeners)
  • useRef — grab a DOM element directly
  • useContext — access shared data without passing props through every layer

When you need to pass data through many nested components, that's prop drilling — and it gets painful. Context solves this by letting any descendant access shared data directly, skipping all the middle layers.

Beyond useState — When State Gets Complex

For simple component-level state, useState is enough. For data shared across many components that changes frequently, external state managers make life easier:

Zustand — a tiny (~1kb) state store. Define your state and actions in one place, any component can subscribe to just the slice it needs. Unlike Context, it only re-renders components that actually use the changed data.

React Query — handles all API/server data. Automatic caching, refetching, loading states, error handling. Replaces the pattern of useEffect + useState + manual loading/error booleans for every API call.

Zod — runtime data validation. TypeScript catches type errors while you code, but disappears at runtime. Zod validates data when the app actually runs — essential for form inputs and API responses.

Tailwind CSS — Utility-First Styling

Instead of writing CSS in separate files, you apply utility classes directly in your markup:

<div className="flex items-center gap-4 p-6 bg-slate-800 rounded-lg">

Responsive design is built in: md:py-8 means "apply 8 units of vertical padding on medium screens and up." Everything is composable, and the cn() utility (combining clsx and tailwind-merge) handles conditional classes and resolves conflicts intelligently.

The Project Structure

After running create-next-app, a Next.js project looks like this:

src/
├── app/              # Routes (pages, layouts, API routes)
├── components/       # Reusable UI pieces
├── lib/              # Utility functions (cn(), date formatting)
└── content/          # MDX files (if using markdown content)

TypeScript enforces type safety across all of it. ESLint catches common mistakes. Prettier formats your code consistently.

Honest Assessment

What clicked: The component model makes intuitive sense once you stop thinking in terms of "one big HTML file." Props and state are just function arguments and local variables with superpowers.

What's still fuzzy: The Server Component vs Client Component boundary feels like it'll take real practice to get right. I understand the theory, but knowing exactly where to draw the line on a real feature is different.

What surprised me: How much of traditional web dev carries over. JSX is just HTML with minor syntax changes. Tailwind classes map directly to CSS properties I already know. The foundation isn't thrown away — it's reorganized.

Key Takeaways

  1. React = describe what the UI should look like for given data, let the framework handle updates
  2. Next.js = React + routing + server rendering + optimization
  3. Server Components = default, no JS shipped, can access server resources
  4. Client Components = opt-in with "use client", for interactivity only
  5. State flows down via props, events flow up via callbacks, Context skips layers for shared data
  6. Zustand/React Query/Zod = the modern toolkit for complex state, server data, and validation

Tomorrow: diving deeper into Server vs Client Components with hands-on data fetching and Suspense boundaries.


Day 1 of 100. The stack is set up. The mental model is forming. Now it's time to build.

Follow along: @KarthNode

Tags
#nextjs#react#typescript#tailwind#web-fundamentals