Introduction to Next.js Project Structure

Introduction

Coming from a formal education background, we often believe that a structured curriculum lays the strongest foundation for learning and mastery.

  • However, in reality, the most effective learning does not always happen through incremental steps.
  • It often begins with having a clear goal or mission for a project, then diving in hands-on and learning along the way.
  • This approach is highly efficient but also the steepest learning curve, as you may have a final blueprint in mind but lack the necessary tools to build the "empire" from the start.

Having successfully created two Progressive Web Apps in Next.js using Firebase Studio, the overall concept of programming is becoming much clearer to me.

  • I have witnessed real-world development in action and experienced the practical considerations needed to make an app run smoothly.
  • At times, switching into coding mode becomes necessary, especially when debugging issues caused by conflicting configurations introduced by AI-generated code.



Understanding the Structure of a Full-Stack Next.js App

Next.js is a modern, full-stack React framework that enables developers to build powerful applications using hybrid rendering (both static and dynamic), API routes and modular architecture.

  • It seamlessly supports both frontend (what users see) and backend (data and logic behind the scenes) components within a single project.

A typical project directory structure looks like this:

  • public/
    • favicons/
    • images/
    • sw.js
    • manifest.json
  • src/
    • app/
    • components/
    • hooks/
    • lib/
    • utils/
  • .env.local
  • next.config.js
  • package.json
  • tsconfig.json



Static Assets - public/Directory

The public/ folder stores static assets like images, icons, and offline fallback pages.

  • Favicons: Define the small icon shown in the browser tab.
  • sw.js: Service worker for offline support of PWA
  • manifest.json: Describes your app (name, icon, colours) for installation of PWA.
  • _offline.html: Optional offline fallback page.

These are served directly to the browser - no import needed.



Frontend UI - app/, components/, and CSS

Web pages are fundamentally made of three layers: HTML, CSS, and JavaScript.

  • app/ folder defines your page structure using React-based routing.
  • layout.tsx provides the global app shell (shared UI like navbar, sidebar, footer, etc.).
    • This layout persists between navigations and preserves state.
  • page.tsx defines individual routes like /, /about, or /blog/[slug].

layout.tsx and page.tsx work together to define the app’s page structure.

  • What UI elements (including components/) appear on every page
  • How dynamic content is inserted.
components/ are modular building blocks that make up your pages, such as buttons, cards, modals, forms, and tables.
  • Each one contains its own HTML structure and interactive logic.
  • Instead of rewriting the same layout multiple times, you create it once on components/ and reuse it - keeping your code organized and consistent.
  • In Next.js App Router, every component is a server component by default. If a component needs client-side interactivity (e.g. state, effects, events), mark those components with 'use client' at the top.
  • Client components run entirely in the browser and enable offline capabilities when combined with browser storage and caching tools like Service Workers or IndexedDB. Server components are critical for securely handling sensitive operations (e.g. authentication, private API access) and performing heavy or private logic (e.g. AI computations, database search)

CSS - Styling your app

  • Global CSS (e.g. globals.css) defines the overall theme and style of the application.
  • If you are using Tailwind CSS, you can build complex designs using utility classes directly in your JSX - reducing the need for separate CSS files.



App Logic – lib/, utils/, and hooks/

JavaScript controls the behaviour of your app - determining what happens when users interact with it (like clicking a button or submitting a form).

  • As your app grows and gains more features, some parts of your logic will be reused across multiple components.
  • Without a proper structure, you would need to dig through long JavaScript files to find and fix issues - a slow and error-prone process.
  • To avoid this, it is important to organize reusable logic into helpers and modules, allowing your app to scale efficiently.
  • This makes your codebase easier to maintain, test, and update as the application evolves.

utils/ - Pure Helper Functions

  • Self-contained logic that works anywhere
  • Examples: slugify(), formatDate(), capitalize()

lib/ - Application Logic & External Services

  • Wrappers for third-party libraries or APIs
  • Often involve side effects like fetching data or using SDKs
  • Examples: firebase.ts, blogger.ts, analytics.ts

hooks/ - Reusable React Logic

  • Custom logic using useState, useEffect, useRef, etc.
  • Only usable in React components
  • Examples: useOnlineStatus(), useDarkMode()

In summary, this separation improves maintainability, scalability, and code clarity.

NOTE: "Utility helpers" are small inline functions used only within a single component. If they are reused across the app, move them to utils/.



Dependencies - package.json

Modern development thrives on code reuse through packages and modules.

Instead of building everything from scratch, developers install high-quality, pre-tested packages. These are listed in package.json.

  • Think of it as the "ingredient list" for your app.
  • After installing via npm install, the functionality is available via import from node_modules.

A library provides pre-written logic to perform specific tasks (e.g., showing a toast notification or syncing to Firebase).

  • A developer then imports these functions (the “what”) and arranges them in a specific order and context (the “how” and “when”) to build meaningful app functionalities.



Configuration Files

next.config.js: Customize or extend Next.js behaviour (e.g. image domains, redirects).

.env.local: Store environment secrets (like API keys); excluded from Git.

tsconfig.json: TypeScript configuration (strictness, paths, etc.).

.gitignore: Prevents committing files like node_modules/, .next or .env.local



Miscellaneous

types/ - Stores TypeScript definitions (interface, type, enum) that define the shape of data.

constants/: Stores reusable hardcoded values (e.g., default limits, labels, category names).

During development, you may notice a .next/ folder appearing in your project root.

  • This directory is automatically generated by Next.js and contains build artifacts, such as compiled pages, route mappings, and cached files.
  • It allows the app to load and rebuild efficiently during development and production builds.



Summary

No matter how complex your stack is, every web app boils down to:

  • HTML - for structure and content.
  • CSS - for style and visual presentation
  • JavaScript - for interactivity and logic.

Everything else - React, Next.js, components, layouts, hooks, and state - is an abstraction layer that simplifies development and scales better.

Comments