Our Journey Migrating Logto SDK Sample to Next.js 13 App Router
Published: 2023-05-25 05:49:37

## Introduction

At Logto, we offer a Next.js SDK named [@logto/next](https://docs.logto.io/docs/recipes/integrate-logto/next-js/), accompanied by a [sample project](https://github.com/logto-io/js/tree/master/packages/next-sample). 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](https://nextjs.org/docs/app) (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](https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration). 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`:

```tsx
'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;
```

### 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:

```tsx
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;
}
```

This function can then be invoked in `page.tsx`:

```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.
```

## 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](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](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.