Skip to content

Commit

Permalink
Migrate from Next.js Pages Router to App Router (#255)
Browse files Browse the repository at this point in the history
## Ticket

Resolves #66

## Changes

This is purely an architecture migration and nothing functionally should
have changed.

**Pages Router → App Router**:

- `pages/_app.tsx` → `app/[locale]/layout.tsx`
- `pages/index.tsx` → `app/[locale]/page.tsx` 
- `pages/health.tsx` → `app/[locale]/health/page.tsx` 
- `pages/api/hello.ts` → `app/api/hello/route.tsx` 

**Internationalized routing and locale detection**:

App Router doesn't include built-in support for internationalized
routing and locale detection like Pages Router.

- Added Next.js Middleware (`src/middleware.ts`) to detect and store the
user's preferred language, and routing them to `/es-US/*` when their
preferred language is Spanish
- Added `i18n/server.ts` to contain server-side configuration for the
`next-intl` plugin. This makes locale strings available to all server
components

## Context for reviewers

- [See the related
ADR](https://github.com/navapbc/template-application-nextjs/blob/main/docs/decisions/app/0008-app-router.md)
for why we're moving towards App Router.
- A page file is restricted in what it can export, hence why `view.tsx`
exists instead of that component being colocated in the `page.tsx` file.
[More context on this Next.js
issue](vercel/next.js#53940 (comment)).
  • Loading branch information
sawyerh authored Dec 20, 2023
1 parent f902b7d commit 799a2ca
Show file tree
Hide file tree
Showing 23 changed files with 264 additions and 209 deletions.
11 changes: 5 additions & 6 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
├── .storybook # Storybook configuration
├── public # Static assets
├── src # Source code
│ ├── app # Routes, layouts, and loading screens
│ │ ├── api # Custom request handlers
│ │ ├── layout.tsx # Root layout, wraps every page
│ │ └── page.tsx # Homepage
│ ├── components # Reusable UI components
│ ├── i18n # Internationalization
│ │ ├── config.ts # Supported locales, timezone, and formatters
│ │ └── messages # Translated strings
│ ├── pages # Page routes and data fetching
│   │ ├── api # API routes (optional)
│   │ └── _app.tsx # Global entry point
│ ├── styles # Sass & design system settings
│ └── types # TypeScript type declarations
├── stories # Storybook pages
Expand All @@ -26,9 +27,7 @@

## 💻 Development

[Next.js](https://nextjs.org/docs) provides the React framework for building the web application. Pages are defined in the `pages/` directory. Pages are automatically routed based on the file name. For example, `pages/index.tsx` is the home page.

Files in the `pages/api` are treated as [API routes](https://nextjs.org/docs/api-routes/introduction). An example can be accessed at [localhost:3000/api/hello](http://localhost:3000/api/hello) when running locally.
[Next.js](https://nextjs.org/docs) provides the React framework for building the web application. Routes are defined in the `app/` directory. Pages are automatically routed based on the directory name. For example, `app/[locale]/about/page.tsx` would render at `/about` (for English) or `/es-US/about` (for Spanish).

[**Learn more about developing Next.js applications** ↗️](https://nextjs.org/docs)

Expand Down
6 changes: 1 addition & 5 deletions app/next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-check
const withNextIntl = require("next-intl/plugin")("./src/i18n/config.ts");
const withNextIntl = require("next-intl/plugin")("./src/i18n/server.ts");
const sassOptions = require("./scripts/sassOptions");

/**
Expand All @@ -15,10 +15,6 @@ const appSassOptions = sassOptions(basePath);
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath,
i18n: {
locales: ["en-US", "es-US"],
defaultLocale: "en-US",
},
reactStrictMode: true,
// Output only the necessary files for a deployment, excluding irrelevant node_modules
// https://nextjs.org/docs/app/api-reference/next-config-js/output
Expand Down
104 changes: 49 additions & 55 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
},
"dependencies": {
"@aws-sdk/client-evidently": "^3.465.0",
"@trussworks/react-uswds": "^6.0.0",
"@trussworks/react-uswds": "^6.1.0",
"@uswds/uswds": "3.7.0",
"lodash": "^4.17.21",
"next": "^14.0.0",
"next-intl": "^3.2.0",
"next": "^14.0.3",
"next-intl": "^3.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand All @@ -41,8 +41,8 @@
"@types/jest-axe": "^3.5.5",
"@types/lodash": "^4.14.202",
"@types/node": "^20.0.0",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.36.0",
Expand Down
3 changes: 3 additions & 0 deletions app/src/app/[locale]/health/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <>healthy</>;
}
32 changes: 32 additions & 0 deletions app/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Root layout component, wraps all pages.
* @see https://nextjs.org/docs/app/api-reference/file-conventions/layout
*/
import { Metadata } from "next";

import Layout from "src/components/Layout";

import "src/styles/styles.scss";

export const metadata: Metadata = {
icons: [`${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/img/logo.svg`],
};

interface LayoutProps {
children: React.ReactNode;
params: {
locale: string;
};
}

export default function RootLayout({ children, params }: LayoutProps) {
return (
<html lang={params.locale}>
<body>
{/* Separate layout component for the inner-body UI elements since Storybook
and tests trip over the fact that this file renders an <html> tag */}
<Layout locale={params.locale}>{children}</Layout>
</body>
</html>
);
}
25 changes: 25 additions & 0 deletions app/src/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Metadata } from "next";
import { isFeatureEnabled } from "src/services/feature-flags";

import { getTranslations } from "next-intl/server";

import { View } from "./view";

interface RouteParams {
locale: string;
}

export async function generateMetadata({ params }: { params: RouteParams }) {
const t = await getTranslations({ locale: params.locale });
const meta: Metadata = {
title: t("home.title"),
};

return meta;
}

export default async function Controller() {
const isFooEnabled = await isFeatureEnabled("foo", "anonymous");

return <View isFooEnabled={isFooEnabled} />;
}
Loading

0 comments on commit 799a2ca

Please sign in to comment.