Writing React and Next.js the "Right Way"
All Updates
ReactJSNextJSWebArchitectureAppRouterServerComponentsDataFetchingFrontendEngineeringJavaScript

Writing React and Next.js the "Right Way"

M
MOXTECH Developers
5 May 20264 min read

Building for the modern web requires moving beyond "how it works" to "how it scales." We break down the absolute essentials of React 19 architecture, Next.js App Router patterns, and why your old habits might be slowing you down.

Introduction: Mastering the Modern Stack: The definition of "good" React code has changed dramatically in the last two years. If you are still building Next.js apps the same way you built them in 2021 (think Pages Router, getServerSideProps, and massive client-side useEffect fetches), you are working harder than necessary and probably delivering a slower user experience.

React and Next.js are now deeply intertwined, especially with the maturity of the App Router paradigm. Writing code "the right way" today is about shifting your mental model from client-first rendering to server-first architecture.

  1. The Core Paradigm Shift: Server Components Default The single biggest change is the arrival of React Server Components (RSC). In the modern App Router architecture, all components are Server Components by default. This isn't just a slight performance optimization; it's a foundational rethink.

The Old Way (Client Fetching): A component loads on the client, mounts, triggers a useEffect, fetches data (creating a network waterfall), and then finally renders.

The "Right" Way (Server First): The component fetches data on the server (securely, directly hitting the database or backend API), renders to static HTML, and streams only the necessary HTML to the client. Zero client-side JS is needed for rendering the initial view.

// app/dashboard/page.tsx (This is a Server Component) import { db } from '@/lib/db'; async function DashboardPage() { // 1. DATA FETCHING HAPPENS HERE, ON THE SERVER const analytics = await db.query('SELECT * FROM analytics'); // 2. THIS IS RENDERED ON THE SERVER return ( <main> <h1>Analytics Dashboard</h1> <AnalyticsChart data={analytics} /> </main> ); }
  1. Embrace Colocation The modern developer experience prioritizes developer velocity and maintainability. A key tenet of "the right way" is colocation. Keep your components, hooks, styles, and tests close to the route that uses them.

Before: components/ vs pages/ (a massive flat components directory with hundreds of files).

Now (Right Way): app/dashboard/_components/, app/dashboard/hooks/, app/dashboard/analytics-chart.tsx. If a component is only used inside the dashboard, it should live inside the dashboard directory. Only promote generic UI components (like Button or Input) to a global shared directory.

  1. Data Flow and Forms: Forget useState for Fetching The traditional "React way" to handle forms involved managing local state with useState and manual onSubmit fetching logic. In Next.js App Router, this is an anti-pattern.

The "right way" utilizes React Actions (and Next.js Server Actions) to handle mutations elegantly. It simplifies the code and works even if JavaScript is slow to load on the client.

// app/posts/new/page.tsx import { createPost } from './actions'; // Use standard HTML form structure export default function NewPostPage() { return ( <form action={createPost}> <input name="title" type="text" placeholder="Post title" /> <textarea name="content" /> <button type="submit">Create Post</button> </form> ); }
// app/posts/new/actions.ts (Sever Action file) 'use server'; import { db } from '@/lib/db'; import { redirect } from 'next/navigation'; export async function createPost(formData: FormData) { // Validate, process, mutate database securely const title = formData.get('title'); await db.insertPost({ title }); // Revalidate cache to show updated data revalidatePath('/posts'); redirect('/posts'); // Redirect back }
  1. Aggressive Caching (But Controlled) Next.js App Router is aggressively cached by default. When you request data, it gets cached at multiple levels (the data cache, the full route cache, and the router cache on the client).

Writing code "the right way" means understanding how to manage this caching behavior, rather than simply disabling it. Use revalidatePath and revalidateTag to purge cached data precisely when a mutation occurs, ensuring user data is always fresh while minimizing unnecessary server load.

Conclusion: The Architecture of Performance The modern React and Next.js ecosystem isn't just about rendering pixels; it’s about architecting full-stack applications where performance, security, and developer experience are unified. By leaning into Server Components, mastering data mutations via Server Actions, and organizing your code through colocation, you are not just writing modern JavaScript you are building resilient, scalable platforms.

Did you find this helpful?

2Click to react