cording12
cording12

Reputation: 166

Adding i18n to a NextJS project with custom middleware routing - accessing the Dynamic variable

Background: I have a NextJS application using version 13.5 and the app router. Using middleware, I have a subdomain of app, as well as a neatly organised local file structure for the main website's homepage.

Desired outcome I would like to introduce i18n in a way that a user can browse the English site without additional path params. So paths would be example.com, example.com/features etc. If the user changes their language, or the site automatically detects their language, the path routing would be example.com/es, example.com/es/features.

Problem: Introduce i18n by using a dynamic route while accommodating my existing middleware. I have followed the NextJS help docs, but since my middleware isn't the most straightforward, I can't seem to figure out the correct syntax to accommodate my current setup + the dynamic route.

Attempted solutions: I've attempted to create [lang] folder inside the first app folder, which would wrap the existing file structure.

In doing this, I changed the NextResponse.rewrite method inside my middleware.ts file to add the dynamic route to the path:

**Before** 
new URL(`/example.com${path === "/" ? "" : path}`, req.url)

**After**
new URL(`[lang]/example.com${path === "/" ? "" : path}`, req.url)

This change allowed the routing to continue working, localhost:3000 and app.localhost:3000. However, it doesn't work when adding language codes to the URL path, i.e. localhost:3000/es

I've tried a multitude of solutions but can't seem to get anything working. I suspect I may need to use rewrites within the next.config.js, but I'm not at all sure how I could get this working.

Any help would be deeply appreciated. Below is extracts of code for reference.

Project setup:

└── project-root
    ├── app
    │   ├── app <-- subdomain; app.localhost:3000
    │   │   ├── (auth)
    │   │   │   ├── *sign-in and sign-out routes here*
    │   │   │   └── layout.tsx
    │   │   └── (dashboard)
    │   │       ├── features
    │   │       │   └── page.tsx
    │   │       ├── page.tsx
    │   │       └── layout.tsx
    │   ├── example.com <-- home page; localhost:3000
    │   │   ├── about
    │   │   │   └── page.tsx
    │   │   ├── features
    │   │   │   └── page.tsx
    │   │   ├── page.tsx
    │   │   └── layout.tsx  
    │   ├── layout.tsx
    │   └── providers.tsx  
    ├── lib
    │   └── middleware
    │       └── app.ts
    ├── middleware.ts
    └── next.config.js

middleware.ts

import { NextFetchEvent, NextRequest, NextResponse } from "next/server"
import { APP_HOSTNAMES, isHomeHostname } from "@/lib/constants"
import { AppMiddleware } from "@/lib/middleware"
import { parse } from "@/lib/middleware/utils"

export const config = {
  matcher: [
    "/((?!api/|_next/|_proxy/|_static|_vercel|[\\w-]+\\.\\w+|).*)",
  ],
}

export default async function middleware(req: NextRequest, ev: NextFetchEvent) {
  const { domain, path, key } = parse(req)
   
  // Rewrite root application to example.com folder
  if (isHomeHostname(domain)) {
    // the example.com below is referring to the local folder name and not the production domain url
    return NextResponse.rewrite(
      new URL(`/example.com${path === "/" ? "" : path}`, req.url)
    )
  }

  // Rewrite for App subdomain. Contains Clerk auth middleware to protect
  // all app.example.com || app.localhost:3000 routes
  if (APP_HOSTNAMES.has(domain)) {
    return AppMiddleware(req, ev)
  }
}

next.config.js

const { withContentlayer } = require("next-contentlayer")

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
}

// export default nextConfig

module.exports = withContentlayer(nextConfig)

app/layout.tsx

/**
 * This is the Root layout which affects all parts of the application.
 * It is inherited by all pages within the app folder and example.com folder.
 */
import "@/styles/globals.css"

import { fontInter } from "@/lib/fonts"
import { Analytics } from "@vercel/analytics/react"

import Providers from "./providers"

interface RootLayoutProps {
  children: React.ReactNode
}

export default function RootLayout({ children }: RootLayoutProps) {
  return (
    /**
     * Hydration warning suppressed due to using next-theme in Providers
     */
    <html
      lang="en"
      suppressHydrationWarning={true}
      className={fontInter.className}
    >
      <body className="bg-background antialiased">
        <Providers>{children}</Providers>
      </body>
      <Analytics />
    </html>
  )
}

Upvotes: 1

Views: 467

Answers (0)

Related Questions