Our Journey Migrating Logto SDK Sample to Next.js 13 App Router

wangsijie

Wang Sijie

Posted on May 25, 2023

Our Journey Migrating Logto SDK Sample to Next.js 13 App Router

Introduction

At Logto, we offer a Next.js SDK named @logto/next, accompanied by a sample project. This project effectively demonstrates how to integrate Logto with Next.js using the traditional "pages" folder, offering examples of API routes, getServerSideProps, Client-side Fetching, and even edge runtime.

Next.js 13 brought us the groundbreaking App Router (previously called the app directory), introducing new features and conventions that quickly became popular in the developer community. Given that Logto’s Next.js SDK fully supports these new features, we were inspired to build a new sample project utilizing this App Router.

In this post, we'll walk you through the details of migrating our old sample project to the new one using the App Router. The project was built upon the old sample project and followed the official migration guide. The process involved several steps: creating pages and layouts, migrating API routes, and utilizing server and client components.

The Migration Process

Page Layout: A New Structure

In our original setup, we utilized an _app.tsx file for setting up the SWR fetcher and an index.tsx file served as the home page.

With the App Router, these become layout.tsx and page.tsx. The layout.tsx file contains the core information, while page.tsx mirrors the functionality of the old index.tsx file.

Client component

One hiccup arose during the first step: setting an "onClick" handler for the button was unsuccessful, yielding an error message stating, "Event handlers cannot be passed to Client Component props. If you need interactivity, consider converting part of this to a Client Component."

To remedy this issue, we extracted the problematic section into a separate component and prefixed the file with use client:

'use client';

type Props = {
  isAuthenticated: boolean;
};

const Nav = ({ isAuthenticated }: Props) => {
  return (
    <nav>
      {isAuthenticated ? (
        <button
          onClick={() => {
            window.location.assign('/api/logto/sign-out');
          }}
        >
          Sign Out
        </button>
      ) : (
        <button
          onClick={() => {
            window.location.assign('/api/logto/sign-in');
          }}
        >
          Sign In
        </button>
      )}
    </nav>
  );
};

export default Nav;
Enter fullscreen mode Exit fullscreen mode

API routes

Transitioning the API routes was as simple as transferring the previous files from /pages/api to /app/api with a few tweaks:

  1. index.ts was renamed to route.ts.
  2. The exported function was renamed to GET or another relevant method name.

Server component

Within the api folder, we have the capability to add server-only functions, which allow React Server Components to fetch data.

In the /app/api/logto/user directory, there's a get-user.tsx file:

import { type LogtoContext } from '@logto/next';
import { cookies } from 'next/headers';

// `server-only` guarantees any modules that import code in file
// will never run on the client. Even though this particular api
// doesn't currently use sensitive environment variables, it's
// good practise to add `server-only` preemptively.
// eslint-disable-next-line import/no-unassigned-import
import 'server-only';
import { config } from '../../../../libraries/config';

export async function getUser() {
  const response = await fetch(`${config.baseUrl}/api/logto/user`, {
    cache: 'no-store',
    headers: {
      cookie: cookies().toString(),
    },
  });

  if (!response.ok) {
    throw new Error('Something went wrong!');
  }

  // eslint-disable-next-line no-restricted-syntax
  const user = (await response.json()) as LogtoContext;

  return user;
}
Enter fullscreen mode Exit fullscreen mode

This function can then be invoked in page.tsx:

import { getUser } from './api/logto/user/get-user';
import Nav from './nav';

const Page = async () => {
  const user = await getUser();

  return (
    <div>
      <header>
        <h1>Hello Logto.</h1>
      </header>
      <Nav isAuthenticated={user.isAuthenticated} />
      {user.isAuthenticated && user.claims && (
        <div>
                    ...
        </div>
      )}
    </div>
  );
}

export default Page.
Enter fullscreen mode Exit fullscreen mode

Conclusion

Upon completing the migration, we found our code and structure significantly more streamlined and intuitive. Although it seemed challenging at the outset, the process was far from daunting. We hope our experience can serve as a valuable guide, helping you confidently migrate your projects to the App Router.

For comparison, here are the links to our projects, both before and after the migration:

Before: https://github.com/logto-io/js/tree/master/packages/next-sample

After: https://github.com/logto-io/js/tree/master/packages/next-app-dir-sample

By examining these projects, you can gain a clearer understanding of the changes and benefits introduced by this migration.

💖 💪 🙅 🚩
wangsijie
Wang Sijie

Posted on May 25, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related