<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sacha</title>
    <description>The latest articles on DEV Community by Sacha (@c-est-sa).</description>
    <link>https://dev.to/c-est-sa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1558103%2Fa7ece4a5-6e16-41ec-bd9d-3d5a44baad54.png</url>
      <title>DEV Community: Sacha</title>
      <link>https://dev.to/c-est-sa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/c-est-sa"/>
    <language>en</language>
    <item>
      <title>Auth with Next.js+Auth.js+MongoDB, and RBAC -bonus topic: JWT</title>
      <dc:creator>Sacha</dc:creator>
      <pubDate>Tue, 22 Jul 2025 11:28:46 +0000</pubDate>
      <link>https://dev.to/c-est-sa/auth-with-nextjsauthjsmongodb-and-rbac-bonus-topic-jwt-16ng</link>
      <guid>https://dev.to/c-est-sa/auth-with-nextjsauthjsmongodb-and-rbac-bonus-topic-jwt-16ng</guid>
      <description>&lt;p&gt;This is a note to self on implementing authentication, using Next.js, Auth.js, and MongoDB, allowing role-based access control, and beginner's notes about JWT as a bonus topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication and RBAC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Next.js

&lt;ul&gt;
&lt;li&gt;Here, we use Server Actions to handle authentication.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://authjs.dev/" rel="noopener noreferrer"&gt;Auth.js&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Here, we use the login method of credentials (email and password).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;MongoDB&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.npmjs.com/package/bcrypt" rel="noopener noreferrer"&gt;bcrypt&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;This is for password hashing.&lt;/li&gt;
&lt;li&gt;We should install TypeScript definitions for bcrypt as well by &lt;code&gt;npm install --save-dev @types/bcrypt&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Directory Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project
 └─ src
     └─ app
     │   └─ page.tsx  &amp;lt;- renders LoginForm.tsx
     └─ components
     │   └─ loginForm.tsx
     └─ lib
     │   └─ actions.ts
     │   └─ db.ts
     └─ types
     │   └─ next-auth.d.ts
     └─ auth.config.ts
     └─ auth.ts
     └─ middleware.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Omitting the styling purpose codes.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;login page with login form
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import LoginForm from "@/components/loginForm";
import { Suspense } from "react";

export default function Home() {
  return (
    &amp;lt;Suspense&amp;gt;
      &amp;lt;LoginForm /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";

import { Button } from "@/components/ui/button";
import { useActionState } from "react";
import { authenticate } from "@/lib/actions";
import { useSearchParams } from "next/navigation";

export default function LoginForm() {
  const searchParams = useSearchParams();
  const callbackUrl =
    searchParams.get("callbackUrl") || "/patient-status-display";
  const [errorMessage, formAction, isPending] = useActionState(
    authenticate,
    undefined
  );

  return (
    &amp;lt;form action={formAction} className="space-y-3"&amp;gt;
      &amp;lt;div className="w-full"&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label htmlFor="email"&amp;gt;
            Email
          &amp;lt;/label&amp;gt;
          &amp;lt;input
            id="email"
            type="email"
            name="email"
            placeholder="Enter your email address"
            required
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label htmlFor="password"&amp;gt;
            Password
          &amp;lt;/label&amp;gt;
          &amp;lt;input
            id="password"
            type="password"
            name="password"
            placeholder="Enter password"
            required
            minLength={6}
          /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;input type="hidden" name="redirectTo" value={callbackUrl} /&amp;gt;

      &amp;lt;Button aria-disabled={isPending}&amp;gt;
        Log in
      &amp;lt;/Button&amp;gt;

      &amp;lt;div
        aria-live="polite"
        aria-atomic="true"
      &amp;gt;
        {errorMessage &amp;amp;&amp;amp; (
          &amp;lt;p className="text-sm text-red-500"&amp;gt;{errorMessage}&amp;lt;/p&amp;gt;
        )}
      &amp;lt;/div&amp;gt;

    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three inputs sent as formData here: &lt;strong&gt;email&lt;/strong&gt;, &lt;strong&gt;password&lt;/strong&gt;, and &lt;strong&gt;redirectTo&lt;/strong&gt;, which is hidden from the UI. RedirectTo is automatically set considering the search params of the page URL, allowing the user to go back to the page that they were on after logging in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server actions
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /lib/actions.ts

"use server";

import { signIn } from "@/auth";
import { AuthError } from "next-auth";

export async function authenticate(
  prevState: string | undefined,
  formData: FormData
) {
  console.log("from actions.ts - ", formData);

  try {
    await signIn("credentials", formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case "CredentialsSignin":
          console.error("Invalid credentials");
          return "Invalid credentials.";
        default:
          console.error("An unexpected error occurred:", error);
          return "Something went wrong.";
      }
    }
    throw error;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The outcome of logging formData to the console is like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;FormData {&lt;br&gt;
  '$ACTION_REF_2': '',&lt;br&gt;
  '$ACTION_2:0': '{"id":"idconsistofrandomcharsandnumbers","bound":"$@1"}',&lt;br&gt;
  '$ACTION_2:1': '["$undefined"]',&lt;br&gt;
  '$ACTION_KEY': 'keyconsistofacharandnumbers',&lt;br&gt;
  email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;',&lt;br&gt;
  password: '123456',&lt;br&gt;
  redirectTo: '/patient-status-display'&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This Server Actions function &lt;code&gt;authenticate()&lt;/code&gt; triggers the Credentials provider's &lt;code&gt;authorize()&lt;/code&gt; function in &lt;code&gt;auth.ts&lt;/code&gt; below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;middleware-purpose code (middleware.ts and auth.ts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-1. middleware.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /middleware.ts

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export default NextAuth(authConfig).auth;

export const config = {
  // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-2. auth.ts (This is necessary separately from &lt;code&gt;middleware.ts&lt;/code&gt; because &lt;code&gt;bcrypt&lt;/code&gt; relies on Node.js APIs not available in Next.js Middleware.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /auth.ts

import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { authConfig } from "./auth.config";

// MongoDB and Auth.js connection purpose things
import { MongoDBAdapter } from "@auth/mongodb-adapter";
import client from "./lib/db";

import { z } from "zod";
import { User } from "@/types/db";
import bcrypt from "bcrypt";


// helper function to get a user from db and return a user object.
async function getUser(email: string): Promise&amp;lt;User | undefined&amp;gt; {
  try {
    // specify the database name and table name
    const db = client.db("Surgery-Status");
    const users = db.collection("User");

    const user = await users.findOne({ email });
    console.log("Raw user result:", user);

    if (!user) {
      return undefined;
    }

    return {
      id: user._id.toString(),
      username: user.username,
      email: user.email,
      password: user.password,
      role: user.role,
    } as User;

  } catch (error) {
    console.error("Failed to fetch user:", error);
    throw new Error("Failed to fetch user.");
  }
}


export const { handlers, auth, signIn, signOut } = NextAuth({
  ...authConfig,
  session: {
    strategy: "jwt",
  },
  // adapter: MongoDBAdapter(client), //edit: this is for session strategy of "database", not "jwt"!
  providers: [
    Credentials({
      async authorize(credentials) {
        console.log("from auth.ts - credentials: ", credentials);

        // validation by Zod
        const parsedCredentials = z
          .object({ email: z.email(), password: z.string().min(6) })
          .safeParse(credentials);

        console.log("from auth.ts - parsedCredentials: ", parsedCredentials);

        if (!parsedCredentials.success) {
          console.log("from auth.ts - Invalid credentials");
          return null;
        }

        // call getUser helper function and retrieve the user from db.
        const { email, password } = parsedCredentials.data;
        const user = await getUser(email);
        console.log("from auth.ts - user: ", user);
        if (!user) return null;

        // check if the password input matches the user's password using bcrypt.
        const passwordsMatch = await bcrypt.compare(password, user.password);
        if (!passwordsMatch) return null;
        return user;
      },
    }),
  ],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outcome of logging credentials:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;{&lt;br&gt;
  '$ACTION_REF_2': '',&lt;br&gt;
  '$ACTION_2:0': '{"id":"608d2e3498577677e1980778f144b62db597e78013","bound":"$@1"}',&lt;br&gt;
  '$ACTION_2:1': '["$undefined"]',&lt;br&gt;
  '$ACTION_KEY': 'k3886862053',&lt;br&gt;
  email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;',&lt;br&gt;
  password: '123456',&lt;br&gt;
  callbackUrl: '/patient-status-display'&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;*the same object as formData&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Outcome of logging parsedCredentials:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;{&lt;br&gt;
  success: true,&lt;br&gt;
  data: { email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;', password: '123456' }&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Outcome of logging raw user result in getUser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;{&lt;br&gt;
  _id: new ObjectId('687a1ecc6e0c2baed2f73a00'),&lt;br&gt;
  username: 'Admin',&lt;br&gt;
  email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;',&lt;br&gt;
  password: '$2b$10$Aio9fK46uuXn5nMidbKhv.uk.Izp3V2ceQltitKWJiisbr4VpFrgq',&lt;br&gt;
  role: 'admin'&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Returned valid user from this &lt;code&gt;authorize()&lt;/code&gt; function if everything is successful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;{&lt;br&gt;
  id: '687a1ecc6e0c2baed2f73a00',&lt;br&gt;
  username: 'Admin',&lt;br&gt;
  email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;',&lt;br&gt;
  password: '$2b$10$Aio9fK46uuXn5nMidbKhv.uk.Izp3V2ceQltitKWJiisbr4VpFrgq',&lt;br&gt;
  role: 'admin'&lt;br&gt;
}&lt;br&gt;
*This returned user object is just returned internally to Auth.js.&lt;br&gt;
-&amp;gt; Then, Auth.js creates a session (JWT), sets a cookie, and redirects the user. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;MongoDB client code

&lt;ul&gt;
&lt;li&gt;taken from &lt;a href="https://authjs.dev/getting-started/adapters/mongodb" rel="noopener noreferrer"&gt;Auth.js docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /lib/db.ts

// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
import { MongoClient, ServerApiVersion } from "mongodb"

if (!process.env.MONGODB_URI) {
  throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
}

const uri = process.env.MONGODB_URI
const options = {
  serverApi: {
    version: ServerApiVersion.v1,
    strict: true,
    deprecationErrors: true,
  },
}

let client: MongoClient

if (process.env.NODE_ENV === "development") {
  // In development mode, use a global variable so that the value
  // is preserved across module reloads caused by HMR (Hot Module Replacement).
  const globalWithMongo = global as typeof globalThis &amp;amp; {
    _mongoClient?: MongoClient
  }

  if (!globalWithMongo._mongoClient) {
    globalWithMongo._mongoClient = new MongoClient(uri, options)
  }
  client = globalWithMongo._mongoClient
} else {
  // In production mode, it's best to not use a global variable.
  client = new MongoClient(uri, options)
}

// Export a module-scoped MongoClient. By doing this in a
// separate module, the client can be shared across functions.
export default client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;auth.config.ts &lt;strong&gt;(RBAC is set here.)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /auth.config.ts

import type { NextAuthConfig } from "next-auth";

export const authConfig = {
  pages: {
    signIn: "/",
  },
  callbacks: {
    jwt({ token, user }) {
      console.log(token, user) //*1
      if (user) {
        token.role = user.role;
      }
      return token;
    },

    session({ session, token }) {
      console.log(sessioin, token) //*2
      if (token &amp;amp;&amp;amp; session.user) {
        session.user.role = token.role as string;
        // if we omit this^, the `role` field won't be available in the session on the browser.
      }
      return session;
    },

    authorized({ auth, request: { nextUrl } }) {
      console.log("auth from authorized callback - ", auth);

      const isLoggedIn = !!auth?.user;
      const userRole = auth?.user?.role;

      // check the route that the user is trying to access.
      const isOnStatusDisplay = nextUrl.pathname.startsWith(
        "/patient-status-display"
      );
      const isOnStatusUpdate = nextUrl.pathname.startsWith(
        "/patient-status-update"
      );
      const isOnPatientInfo = nextUrl.pathname.startsWith(
        "/patient-information"
      );

      // redirect unauthenticated users to login page.
      // patient-status-display page is available for unauthenticated users, so it's not included in the condition.
      if (!isLoggedIn &amp;amp;&amp;amp; (isOnStatusUpdate || isOnPatientInfo)) {
        return false;
      }

      // if already authenticated, do role-based access control.
      if (isLoggedIn) {
        // admin can access all pages
        if (userRole === "admin") {
          return true;
        }

        // member cannot access patient-information. Redirect to patient-status-display in which case.
        if (userRole === "member" &amp;amp;&amp;amp; isOnPatientInfo) {
          return Response.redirect(new URL("/patient-status-display", nextUrl));
        }

        // member cannot access the unavailable pages. Redirect to patient-status-display in which case.
        if (!isOnStatusDisplay &amp;amp;&amp;amp; !isOnStatusUpdate &amp;amp;&amp;amp; !isOnPatientInfo) {
          return Response.redirect(new URL("/patient-status-display", nextUrl));
        }

        // member can access the pages which is outside the conditions above.
        return true;
      }

      return true;
    },
  },
  providers: [], // this is just to satisfy the specified structure.
} satisfies NextAuthConfig;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pages&lt;/strong&gt; option allows us to specify the route for custom sign-in, sign-out, and error pages. By setting this, the user will be redirected to our custom login page, rather than the NextAuth.js default page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Callbacks&lt;/strong&gt; option contains three functions. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jwt&lt;/strong&gt; function is necessary to take the custom fields (in this case, &lt;code&gt;role&lt;/code&gt;) from the user object (which is returned from DB) and store them in the JWT. This runs at login, and then the JWT is stored in the cookie on the browser.&lt;/p&gt;

&lt;p&gt;Outcome of logging token and user (*1) (if a user is logged in):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;token: {&lt;br&gt;
  email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;',&lt;br&gt;
  sub: '687a1ecc6e0c2baed2f73a00',&lt;br&gt;
  role: 'admin',&lt;br&gt;
  iat: 1753258099,&lt;br&gt;
  exp: 1755850099,&lt;br&gt;
  jti: 'a82b44b4-c226-4216-bba5-63100f9e134e'&lt;br&gt;
}&lt;br&gt;
user: undefined&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Session&lt;/strong&gt; function is necessary to define what should be returned as a session from &lt;code&gt;useSession()&lt;/code&gt; (in the client) or &lt;code&gt;getServerSession()&lt;/code&gt; (in the server), or &lt;code&gt;auth()&lt;/code&gt; (for both client and server) (&lt;a href="https://authjs.dev/getting-started/session-management/get-session?framework=Next.js" rel="noopener noreferrer"&gt;related part in Auth.ts docs&lt;/a&gt;). This runs when the client or the server asks for session data, copying the custom fields from the JWT in this case. &lt;em&gt;(Here, we are talking about the case where the session strategy is JWT, not database.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Outcome of logging session and token (*2) (if a user is logged in):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;session: {&lt;br&gt;
  user: { name: undefined, email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;', image: undefined },&lt;br&gt;
  expires: '2025-08-22T08:08:19.085Z'&lt;br&gt;
}&lt;br&gt;
token: {&lt;br&gt;
  email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;',&lt;br&gt;
  sub: '687a1ecc6e0c2baed2f73a00',&lt;br&gt;
  role: 'admin',&lt;br&gt;
  iat: 1753258099,&lt;br&gt;
  exp: 1755850099,&lt;br&gt;
  jti: 'a82b44b4-c226-4216-bba5-63100f9e134e'&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Authorized&lt;/strong&gt; function is in charge of protecting the routes, depending on whether or not the user is authenticated, or which role the user has. The property &lt;code&gt;auth&lt;/code&gt; contains the user's session, and the &lt;code&gt;request&lt;/code&gt; property contains the incoming request.&lt;br&gt;
Under the hood, the following steps are happening...&lt;br&gt;
-1. This grabs the JWT stored in the cookie.&lt;br&gt;
-2. Then, verifies and decodes it using the NEXTAUTH_SECRET.&lt;br&gt;
(-3. Then, if necessary, runs &lt;code&gt;jwt()&lt;/code&gt; callback to reconstruct the final token.)&lt;br&gt;
-4. Finally, converts that decoded token into an object, which is available in the function as a variable &lt;code&gt;auth&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Outcome of logging &lt;code&gt;auth&lt;/code&gt; (if a user is logged in):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;{&lt;br&gt;
  user: { email: '&lt;a href="mailto:admin@email.com"&gt;admin@email.com&lt;/a&gt;', role: 'admin' },&lt;br&gt;
  expires: '2025-08-19T11:38:31.810Z'&lt;br&gt;
}&lt;br&gt;
*The expiration date comes from the format of JWT; by default, JWT includes &lt;code&gt;exp&lt;/code&gt; claim (≒ field) when it's created.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Update the NextAuth types to avoid type errors
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /types/next-auth.d.ts

import NextAuth from "next-auth";

declare module "next-auth" {
  interface Session {
    user: {
      id: string;
      email: string;
      role: string;
    } &amp;amp; DefaultSession["user"];
  }

  interface User {
    id: string;
    email: string;
    role: string;
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    role: string;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What I would like to explore further
&lt;/h3&gt;

&lt;p&gt;In this case, I used Next.js Server Actions to access the database. Now I'm wondering how I can use the backend constructed with Node.js+Express.js to handle authentication with this stack, because I use the backend outside Next.js for the other APIs in this project for some reason. I also would like to know whether it's generally recommended to let the backend handle authentication as well in this case, or it's alright to use Server Actions just for authentication.&lt;/p&gt;




&lt;h2&gt;
  
  
  Side Note - JWT vs database as a session strategy
&lt;/h2&gt;

&lt;p&gt;Session is the way we keep track of the logged-in user for authorization purposes.&lt;br&gt;
There are two types of strategies how we manage sessions —database (session ID) and JWT.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;database (session ID)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is stateful, meaning the server has the information necessary for authorization. The only thing transmitted is just session ID, and using it, the server needs to access the storage to extract the necessary info for authorization.&lt;/li&gt;
&lt;li&gt;When a user logs in, the server issues a session ID.&lt;/li&gt;
&lt;li&gt;Server stores the session ID along with the user's information in the session storage.&lt;/li&gt;
&lt;li&gt;Client stores the issued session ID in a cookie. Client sends this cookie every time it makes requests.&lt;/li&gt;
&lt;li&gt;Server accesses the stored user's info, verifies the authorization status based on the session ID contained in the cookie sent with the request.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;JWT&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is stateless, meaning the server doesn't have the information necessary for authorization. JWT itself contains it, so all the server has to do is just check it.&lt;/li&gt;
&lt;li&gt;When a user logs in, the server issues a JWT.&lt;/li&gt;
&lt;li&gt;Client stores the issued JWT in a cookie. Client adds this JWT to the header of the request when making it.&lt;/li&gt;
&lt;li&gt;Server verifies the JWT and its signature.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;References&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/learn/dashboard-app/adding-authentication" rel="noopener noreferrer"&gt;Learn Next.js - 15. Adding Authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://authjs.dev/getting-started/adapters/mongodb" rel="noopener noreferrer"&gt;Auth.js MongoDB Adapter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/ippe/articles/jwt-auth-ippei" rel="noopener noreferrer"&gt;JWTを使った認証・認可の仕組みから実装まで理解する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mamezou-tech.com/blogs/2022/12/08/jwt-auth/" rel="noopener noreferrer"&gt;基本から理解するJWTとJWT認証の仕組み&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>authjs</category>
      <category>mongodb</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Combining Client and Server Components in Next.js for Better Performance</title>
      <dc:creator>Sacha</dc:creator>
      <pubDate>Tue, 08 Jul 2025 07:03:44 +0000</pubDate>
      <link>https://dev.to/c-est-sa/combining-client-and-server-components-in-nextjs-for-better-performance-42a7</link>
      <guid>https://dev.to/c-est-sa/combining-client-and-server-components-in-nextjs-for-better-performance-42a7</guid>
      <description>&lt;p&gt;This is to explore how we can combine client and server components for better performance. We will use a search bar functionality utilizing URL search params (not state) in an app as an example case. &lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Page component

import Pagination from "@/app/ui/invoices/pagination";
import Search from "@/app/ui/search";
import Table from "@/app/ui/invoices/table";
import { CreateInvoice } from "@/app/ui/invoices/buttons";
import { lusitana } from "@/app/ui/fonts";
import { InvoicesTableSkeleton } from "@/app/ui/skeletons";
import { Suspense } from "react";

export default async function Page(props: {
  searchParams?: Promise&amp;lt;{
    query?: string;
    page?: string;
  }&amp;gt;;
}) {
  const searchParams = await props.searchParams;
  const query = searchParams?.query || "";
  const currentPage = Number(searchParams?.page) || 1;

  return (
    &amp;lt;div className="w-full"&amp;gt;
      &amp;lt;div className="flex w-full items-center justify-between"&amp;gt;
        &amp;lt;h1 className={`${lusitana.className} text-2xl`}&amp;gt;Invoices&amp;lt;/h1&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div className="mt-4 flex items-center justify-between gap-2 md:mt-8"&amp;gt;
        &amp;lt;Search placeholder="Search invoices..." /&amp;gt;
        &amp;lt;CreateInvoice /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;Suspense key={query + currentPage} fallback={&amp;lt;InvoicesTableSkeleton /&amp;gt;}&amp;gt;
        &amp;lt;Table query={query} currentPage={currentPage} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
      &amp;lt;div className="mt-5 flex w-full justify-center"&amp;gt;
        {/* &amp;lt;Pagination totalPages={totalPages} /&amp;gt; */}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Search component

"use client";

import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useDebouncedCallback } from "use-debounce";

export default function Search({ placeholder }: { placeholder: string }) {
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const { replace } = useRouter();

  const handleSearch = useDebouncedCallback((term: string) =&amp;gt; {
    const params = new URLSearchParams(searchParams);
    if (term) {
      params.set("query", term);
    } else {
      params.delete("query");
    }
    replace(`${pathname}?${params.toString()}`);
  }, 300);

  return (
    &amp;lt;div className="relative flex flex-1 flex-shrink-0"&amp;gt;
      &amp;lt;label htmlFor="search" className="sr-only"&amp;gt;
        Search
      &amp;lt;/label&amp;gt;
      &amp;lt;input
        className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
        placeholder={placeholder}
        onChange={(e) =&amp;gt; handleSearch(e.target.value)}
        defaultValue={searchParams.get("query")?.toString()}
      /&amp;gt;
      &amp;lt;MagnifyingGlassIcon className="absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// InvoicesTable component

import Image from 'next/image';
import { UpdateInvoice, DeleteInvoice } from '@/app/ui/invoices/buttons';
import InvoiceStatus from '@/app/ui/invoices/status';
import { formatDateToLocal, formatCurrency } from '@/app/lib/utils';
import { fetchFilteredInvoices } from '@/app/lib/data';

export default async function InvoicesTable({
  query,
  currentPage,
}: {
  query: string;
  currentPage: number;
}) {
  const invoices = await fetchFilteredInvoices(query, currentPage);

  return (
    &amp;lt;div className="mt-6 flow-root"&amp;gt;
      &amp;lt;div className="inline-block min-w-full align-middle"&amp;gt;
        &amp;lt;div className="rounded-lg bg-gray-50 p-2 md:pt-0"&amp;gt;
          &amp;lt;div className="md:hidden"&amp;gt;
            {invoices?.map((invoice) =&amp;gt; (
              &amp;lt;div
                key={invoice.id}
                className="mb-2 w-full rounded-md bg-white p-4"
              &amp;gt;
              . . . 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How this search bar functionality with URL search params works
&lt;/h2&gt;

&lt;p&gt;The key to understanding what's happening is how Next.js App Router handles URL search parameters and passes them to page components.&lt;br&gt;
The overview of the complete flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User types in search box
         ↓
2. Search component calls handleSearch()
         ↓
3. URL gets updated to /dashboard/invoices?query=searchTerm
         ↓
4. Next.js detects URL change, re-renders Page component with new searchParams
         ↓
5. Page component extracts query and currentPage
         ↓
6. These get passed to Table component
         ↓
7. Table component fetches filtered data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the breakdown of each step.&lt;/p&gt;

&lt;p&gt;-1. User types in search box&lt;br&gt;
Nothing to say here. Just let them type whatever!&lt;/p&gt;

&lt;p&gt;-2. Search component calls handleSearch()&lt;br&gt;
The &lt;code&gt;handleSearch&lt;/code&gt; function in the &lt;code&gt;Search&lt;/code&gt; component gets called, creates searchParams for the URL and updates it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const handleSearch = useDebouncedCallback((term: string) =&amp;gt; {
    // Creates a new URLSearchParams object
    const params = new URLSearchParams(searchParams);

    // Sets ?query=searchTerm or delete the parameter
    if (term) {
      params.set("query", term);
    } else {
      params.delete("query");
    }

    // Updates the URL without a page refresh
    replace(`${pathname}?${params.toString()}`);
  }, 300);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-3. URL gets updated to /dashboard/invoices?query=searchTerm&lt;br&gt;
Thanks to the previous step!&lt;/p&gt;

&lt;p&gt;-4. Next.js detects URL change, re-renders Page component with new searchParams&lt;br&gt;
This occurs without a full page reload thanks to the Next.js App Router feature.&lt;/p&gt;

&lt;p&gt;-5. Page component extracts query and currentPage&lt;br&gt;
This is the Next.js App Router's nature. When we have a &lt;code&gt;Page&lt;/code&gt; component, Next.js App Router automatically passes certain props to it based on the URL and route structure. This includes &lt;code&gt;searchParams&lt;/code&gt;.&lt;br&gt;
SearchParams has a JavaScript object-like structure. &lt;br&gt;
E.g.) the search params for the URL &lt;code&gt;/dashboard/invoices?query=john&amp;amp;page=2&lt;/code&gt; would look like this: &lt;code&gt;{query: 'john', page: '2'}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here, Next.js automatically extracts the search parameters from the URL and passes them to the page component as the searchParams prop.&lt;br&gt;
When the URL is &lt;code&gt;/dashboard/invoices?query=john&amp;amp;page=2&lt;/code&gt;, the extracted search params will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  searchParams: Promise&amp;lt;{
    query: "john",
    page: "2"
  }&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-6. These get passed to Table component in the Page component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const searchParams = await props.searchParams;
  const query = searchParams?.query || "";
  const currentPage = Number(searchParams?.page) || 1;

  return (
    . . .
      &amp;lt;Table query={query} currentPage={currentPage} /&amp;gt;
    . . .
  )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-7. Table component fetches filtered data&lt;br&gt;
It calls &lt;code&gt;fetchFilteredInvoices()&lt;/code&gt; function passing query and currentPage as the arguments.&lt;/p&gt;
&lt;h2&gt;
  
  
  Side Note
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Notice that the Search component is a client component, whereas the Page and the Table components are server components. The Search component has to interact with the user input and manipulate the URL, so it &lt;strong&gt;must&lt;/strong&gt; be a client component.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Notice that, in order to extract the URL search parameters, we use &lt;code&gt;useSearchParams()&lt;/code&gt; in the Search component (which is a client component) because accessing the URL directly with this method on the client is faster than waiting for Next.js to send the info from the server to the client.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// if it's on the client 
const searchParams = useSearchParams();
const query = searchParams.get('query'); // &amp;lt;- Instant, no network ◎

// if it's on the server
const { searchParams } = props;
const query = searchParams?.query; // &amp;lt;- Needs server rendering △
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Notice that, in order to fetch the data and show the table according to the URL search parameters, we use &lt;code&gt;searchParams&lt;/code&gt; prop in the Page component (which is a server component) and then pass them to the Table component (also a server component) because this way takes fewer network trips than doing everything from the client.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// if it's on the client
"use client";
function ClientComponent () {
  const searchParams = useSearchParams();
  const query = searchParams.get('query');

  const [data, setData] = useState(null);
  useEffect(() =&amp;gt; {
    fetch(`/api/invoices?query=${query}`)
      .then(res =&amp;gt; res.json())
      .then(setData);
  }, [query]);

  return &amp;lt;div&amp;gt;{data?.map(...)}&amp;lt;/div&amp;gt;;
}
// ^ Needs to call the data fetching API using the search params AFTER loading the page structure (empty placeholder) with JS code, resulting in double network trips △

// if it's on the server
async function ServerComponent ({ searchParams }) {
  const query = searchParams?.query;
  const data = await fetchFilteredInvoices(query); // &amp;lt;- Direct DB access
  return &amp;lt;div&amp;gt;{data.map(...)}&amp;lt;/div&amp;gt;;
}
// ^ This approach can get the page structure AND the data at the same time, resulting in only one network trip ◎
// Server does this BEFORE sending a response on the first request:
// 1. Next.js extracts searchParams from URL
// 2. Page component receives searchParams  
// 3. Table component gets query prop
// 4. fetchFilteredInvoices() runs on server
// 5. Server gets data from database
// 6. Server renders complete HTML with data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;The key difference is when the URL search parameters are available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Client-Side Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Server sends HTML (searchParams not available yet)
2. JavaScript loads in browser
3. useSearchParams() can now read URL
4. Now make API request with parameters
visualized:
Browser                              Server
   │                                    │
   │ ──── GET /invoices?query=john ───&amp;gt; │
   │                                    │ (can't access searchParams until JS runs in browser)
   │ &amp;lt;────── HTML + JS (no data) ────── │
   │                                    │
   │        (JS runs, reads URL)        │
   │                                    │
   │ ── GET /api/invoices?query=john ─→ │
   │ &amp;lt;────────── JSON data ──────────── │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server-Side Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Next.js extracts searchParams from URL immediately
2. Server components can use searchParams right away
3. Server fetches data and renders complete HTML
4. Send everything together
visualized:
Browser                          Server
   │                                │
   │ ── GET /invoices?query=john ─&amp;gt; │
   │                                │ (Next.js immediately extracts searchParams)
   │                                │     ↓
   │                                │ fetchFilteredInvoices(query)
   │                                │     ↓
   │ &amp;lt;─ Complete HTML with data ─── │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;References:&lt;br&gt;
&lt;a href="https://nextjs.org/learn/dashboard-app/adding-search-and-pagination" rel="noopener noreferrer"&gt;Learn Next.js Chapter 11&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>📖 Course Menu - Efficient Data Fetching in Next.js</title>
      <dc:creator>Sacha</dc:creator>
      <pubDate>Mon, 07 Jul 2025 05:35:37 +0000</pubDate>
      <link>https://dev.to/c-est-sa/course-menu-efficient-data-fetching-in-nextjs-4ho2</link>
      <guid>https://dev.to/c-est-sa/course-menu-efficient-data-fetching-in-nextjs-4ho2</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: I'm always talking about server components here.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🍸 Aperitif - Waterfall vs Parallel Data Fetching
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Waterfall Data Fetching
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Sequence of await usages

&lt;ul&gt;
&lt;li&gt;Wait until the previous fetch is complete.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Parallel Data Fetching
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Promise.all()

&lt;ul&gt;
&lt;li&gt;When one of the fetches is slower than the others, &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  🥗 Hors d'oeuvres - Static vs Dynamic Rendering
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Static Rendering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Data fetching and rendering happen only when 

&lt;ul&gt;
&lt;li&gt;building the application (during deployment)&lt;/li&gt;
&lt;li&gt;revalidating data&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The cached data is served whenever a user visits the application.&lt;/li&gt;

&lt;li&gt;Pros:

&lt;ul&gt;
&lt;li&gt;Pre-rendered content can be cached when deploying, resulting in &lt;strong&gt;faster websites&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The server doesn't have to send the content because it's already cached, resulting in &lt;strong&gt;reduced server load&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search engines can read the pre-rendered content, resulting in &lt;strong&gt;improved SEO&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Suitable for webpages with no data or data that is common for the users, such as a static blog post or a product page.  Not a good fit for data-heavy pages.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dynamic Rendering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Data fetching and rendering happen when a user visits the page (at request time).&lt;/li&gt;
&lt;li&gt;Pros:

&lt;ul&gt;
&lt;li&gt;Allows us to show &lt;strong&gt;real-time and up-to-date data&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Makes it easier to deal with &lt;strong&gt;personalized content&lt;/strong&gt;, from showing it to updating it based on user interactions.&lt;/li&gt;
&lt;li&gt;Allows us to &lt;strong&gt;access the information that we can only know at the request time&lt;/strong&gt;, such as cookies or the URL search parameters.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;If one of the requests is slow, the app is only as fast as the slowest request.&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  🥘 Main - Streaming
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Streaming
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A data transfer technique that allows us to break down a route into smaller chunks and progressively stream them from the server to the client as they become ready.&lt;/li&gt;
&lt;li&gt;Pros:

&lt;ul&gt;
&lt;li&gt;Slow data requests don't block the entire page from showing. The user can interact with the ready parts of the page.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to implement this with Next.js:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;At the page level, with the &lt;code&gt;loading.tsx&lt;/code&gt; file (which creates &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;)

&lt;ul&gt;
&lt;li&gt;We can just have a &lt;code&gt;loading.tsx&lt;/code&gt; file in the route, then Next.js presents it while the content is loading.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;At the component level, with &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Move the data fetching logic to the child component that we wanna stream.&lt;/li&gt;
&lt;li&gt;In the parent component, wrap the child component with &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; and pass props of a fallback component.&lt;/li&gt;
&lt;li&gt;We can also have a component to wrap multiple components to load them at the same time. (streaming for the section)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  ☕️ Dessert - Partial Prerendering (PPR)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Experimental in Next.js 14.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combination of static rendering, dynamic rendering, and streaming on one route.&lt;/li&gt;
&lt;li&gt;When the user visits the PPR route,

&lt;ul&gt;
&lt;li&gt;a static route shell containing static content is served first. (Static content is actually prerendered during build time or revalidation.)&lt;/li&gt;
&lt;li&gt;the shell leaves holes where dynamic content will load asynchronously.&lt;/li&gt;
&lt;li&gt;the async holes are streamed in parallel.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; plays a role as a boundary between the static and dynamic content. When we wrap a component with &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;, we are telling Next.js to treat the wrapped content dynamically.&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;References:&lt;br&gt;
&lt;a href="https://nextjs.org/learn/dashboard-app/static-and-dynamic-rendering" rel="noopener noreferrer"&gt;Learn Next.js Chapter 8 - 10&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Git Rebase 101 (What, Why, When, and How)</title>
      <dc:creator>Sacha</dc:creator>
      <pubDate>Fri, 27 Sep 2024 13:20:13 +0000</pubDate>
      <link>https://dev.to/c-est-sa/git-rebase-101-what-why-when-and-how-b1h</link>
      <guid>https://dev.to/c-est-sa/git-rebase-101-what-why-when-and-how-b1h</guid>
      <description>&lt;p&gt;The other day, I had an invaluable opportunity to learn about &lt;code&gt;git rebase&lt;/code&gt;. It took me some time to fully understand the command's behaviour and its benefits. This article is kind of my learning note, but I hope this will also help fellow developers out there deepen their knowledge! Also, if you find anything incorrect or have something to say, please feel free to leave comments or reach out to me on any platform!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Git Rebase?
&lt;/h2&gt;

&lt;p&gt;Git rebase is a git command that lets you integrate a history in one branch to another, without creating additional merge commits. In other words, when using it, all the commit history appears on one branch linearly without additional merge commits, resulting in looking as if all the changes were made on the branch upfront and there was no other working branch in the first place. &lt;/p&gt;

&lt;h3&gt;
  
  
  Visualized Example
&lt;/h3&gt;

&lt;p&gt;The initial state: A feature branch is created from the commit A. Then, three new commits are created on the main branch (B, C, D), and two commits are created on the feature branch (X, Y).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   main: A -- B -- C -- D
feature: A -- X -- Y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before merging the feature branch to the main branch, we have to reflect the new commits on the main branch (B, C, D) to the feature branch. This is the case where git rebase comes in handy. We can run git rebase on the feature branch. The histories will look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   main: A -- B -- C -- D
feature: A -- B -- C -- D -- X -- Y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the three commits on the main branch (B, C, D) are inserted into the feature branch before the commits X and Y. (Potentially, we might see some conflicts here, in which case we should solve them manually.) &lt;/p&gt;

&lt;p&gt;Now, we can peacefully merge the feature branch to the main. The histories after the merge will look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   main: A -- B -- C -- D -- X -- Y
feature: A -- B -- C -- D -- X -- Y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Technically, the commits X and Y after rebasing are not the same as the ones before rebasing. In this example case, the rebase command performed on the feature branch rewinds the commit history to the root commit (A), places the commits to be integrated (B, C, D), and re-creates the commits rewinded (X, Y) again (we can call them X' and Y'). So, the commit X' and Y' are different commits from X and Y that contain the same information as X and Y. &lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   main: A -- B -- C -- D
feature: A -- X -- Y

  ↓ rebase main to feature

   main: A -- B -- C -- D
feature: A -- B -- C -- D -- X' -- Y'

  ↓ merge feature to main

   main: A -- B -- C -- D -- X' -- Y'
feature: A -- B -- C -- D -- X' -- Y'
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Git Rebase?
&lt;/h2&gt;

&lt;p&gt;One thing to notice from the example above is there's no merge commit on either the main or feature branches. This is the main reason why we want to use this command. &lt;/p&gt;

&lt;p&gt;To appreciate this more clearly, let me take a look at the case where we use git pull and merge on the example above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   main: A -- B -- C -- D
feature: A -- X -- Y

  ↓ pull and merge main to feature

   main: A -- B -- C -- D
feature: A -- X -- Y -- M(B--C--D)

  ↓ merge feature to main

   main: A -- B -- C -- D -- M(X--Y--M(B--C--D))
feature: A -- X -- Y -- M(B--C--D)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can obviously see that the histories are getting complex because of the merge commits. Git rebase is a powerful way to avoid this. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Why do we want to keep the histories simple? There could be a lot of answers to this question, but the main idea is to make it easier to understand who did what and when. This would be beneficial for code reviewers, project managers, and newly joined team members who are trying to catch up with the team.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When to Git Rebase?
&lt;/h2&gt;

&lt;p&gt;We should use git rebase when...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we want to integrate some commits from one branch into another, keeping the histories simple. 

&lt;ul&gt;
&lt;li&gt;Basically, when we want to perform git pull + merge, rebase could be an alternative. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  When NOT to?
&lt;/h3&gt;

&lt;p&gt;We should not use git rebase when...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the branch whose history we want to add changes to is shared with others. 

&lt;ul&gt;
&lt;li&gt;Since git rebase changes the commit history (as seen on the feature branch from the example case above,) if we perform git rebase on a shared branch, it will cause confusion and conflicts with the branch in the others' environment. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;the commits are already pushed to GitHub. 

&lt;ul&gt;
&lt;li&gt;This is actually prohibited. If you perform git rebase on such commits and try to push them to GitHub again, GitHub doesn't accept this push. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;we can expect a lot of conflicts. 

&lt;ul&gt;
&lt;li&gt;Because the command checks every commit to find whether there are conflicts and stops the process if found till the conflict is resolved, it would be troublesome to use this when too many conflicts are expected. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;we want to keep merge and branch integration histories. 

&lt;ul&gt;
&lt;li&gt;If we intentionally want to keep them, of course we should not use rebase. Git pull + merge would be the way. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Git Rebase?
&lt;/h2&gt;

&lt;p&gt;Let me re-follow the example case with the actual commands we should run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial state:
   main: A -- B -- C -- D
feature: A -- X -- Y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1.Checkout to the feature branch (&lt;code&gt;git checkout feature&lt;/code&gt;)&lt;br&gt;
2.Perform git rebase (&lt;code&gt;git rebase main&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result of step #2:
   main: A -- B -- C -- D
feature: A -- B -- C -- D -- X' -- Y'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Checkout to the main branch and merge feature to main (&lt;code&gt;git checkout main&lt;/code&gt; then &lt;code&gt;git merge feature&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result of step #3:
   main: A -- B -- C -- D -- X' -- Y'
feature: A -- B -- C -- D -- X' -- Y'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 In the real-world scenario, step #3 is most likely to be replaced by pushing the feature branch to GitHub and creating a PR to merge it to the main.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
