Secure paths with [local] section of the URL

When it comes to managing app routes for an i18n application, I initially relied on reading the request headers (cookies and accept-language) to determine the locale. The experimental typedRoutes feature was working perfectly fine in this setup. However, I recently received advice to move the locale information into the pathname itself. Even though I can still construct paths without directly including the locale segment, the middleware is now responsible for redirecting to the correct path based on the same lookup method as before. While this approach eliminates a round-trip and reduces computational load, it might be more efficient to include the current locale in the path from the beginning. For reference, I am using i18next.

// src/i18n/settings.ts

import { InitOptions } from "i18next";
import type { Route } from "next";

import * as config from "@/services/config-service/service-public.mjs";

export const SUPPORTED_LOCALES = ["en-US"] as const;

export const FALLBACK_LOCALE = SUPPORTED_LOCALES[0];

export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];

export const cookieName = config.i18nName;

export const getOptions = () =>
    ({
        debug: config.debugI18n,
        fallbackLng: FALLBACK_LOCALE,
        supportedLngs: SUPPORTED_LOCALES
    }) satisfies InitOptions as InitOptions;

export const i18nPathname = <
    Locale extends SupportedLocale,
    LogicalPathname = string extends Route<`/${Locale}${infer Pathname}`>
        ? Pathname
        : never
>(
    locale: SupportedLocale,
    logicalPathname: LogicalPathname
) => (logicalPathname === "/" ? `/${locale}` : `/${locale}${logicalPathname}`);
// some random page or action

redirect(i18nPathname(locale, "/profile/welcome"));
redirect(`/${locale}/profile/welcome`);

Although I haven't encountered any type errors with this setup, the return type of i18nPathname() remains as string. Furthermore, there seems to be no assistance from autocomplete. At this point, the distinction between my function and direct interpolation is unclear.

  1. Is there a way to achieve correct pathname typing (including autocomplete and typo checks)?
  2. Are there alternative approaches that could help me reach my desired outcome?

I have observed the path generation in TanStack Router and would love to implement something similar with Next!


Edit:

Below is the structure of my app folder, which should contain nothing unexpected.

t --only-dirs src/app
src/app/
├── [locale]/
│  ├── _actions/
│  ├── about/
│  ├── design-system/
│  │  ├── cards/
│  │  ├── elevation/
│  │  ├── input/
│  │  ├── interactive/
│  │  ├── palette/
│  │  ├── table/
│  │  └── typography/
│  ├── profile/
│  │  ├── verification/
│  │  │  └── _actions/
│  │  ├── verification-prompt/
│  │  │  └── _actions/
│  │  └── welcome/
│  ├── profiles/
│  │  └── new/
│  │     └── _actions/
│  └── sessions/
│     ├── assistance/
│     │  └── password/
│     │     ├── _actions/
│     │     ├── new/
│     │     │  └── _actions/
│     │     └── prompt/
│     └── new/
│        └── _actions/
└── api/
   └── cron/
      ├── password-resets/
      └── sessions/

Edit 2:

This is how Route is defined by Next:

export type Route<T extends string = string> =
  __next_route_internal_types__.RouteImpl<T>

In the same file (.next/types/link.d.ts), here's an excerpt of __next_route_internal_types__. Keep in mind that StaticRoutes and DynamicRoutes may vary from one application to another.

type SafeSlug<S extends string> = S extends `${string}/${string}`
  ? never
  : S extends `${string}${SearchOrHash}`
  ? never
  : S extends ''
  ? never
  : S

// ...

type StaticRoutes = 
  | `/api/cron/password-resets`
  | `/api/cron/sessions`
type DynamicRoutes<T extends string = string> = 
  | `/${SafeSlug<T>}`
  | `/${SafeSlug<T>}/about`
  | `/${SafeSlug<T>}/profile/verification`
  | `/${SafeSlug<T>}/profile/verification-prompt`
  | `/${SafeSlug<T>}/sessions/assistance`
  | `/${SafeSlug<T>}/sessions/assistance/password/new`
  | `/${SafeSlug<T>}/profile`
  | `/${SafeSlug<T>}/profile/welcome`
  | `/${SafeSlug<T>}/sessions/assistance/password`
  | `/${SafeSlug<T>}/sessions/new`
  | `/${SafeSlug<T>}/sessions/assistance/password/prompt`
  | `/${SafeSlug<T>}/sessions`
  | `/${SafeSlug<T>}/profiles/new`
  | `/${SafeSlug<T>}/design-system/input`
  | `/${SafeSlug<T>}/design-system`
  | `/${SafeSlug<T>}/design-system/palette`
  | `/${SafeSlug<T>}/design-system/table`
  | `/${SafeSlug<T>}/design-system/cards`
  | `/${SafeSlug<T>}/design-system/interactive`
  | `/${SafeSlug<T>}/design-system/typography`
  | `/${SafeSlug<T>}/design-system/elevation`

type RouteImpl<T> = 
  | StaticRoutes
  | SearchOrHash
  | WithProtocol
  | `${StaticRoutes}${SearchOrHash}`
  | (T extends `${DynamicRoutes<infer _>}${Suffix}` ? T : never)

I do plan on incorporating additional dynamic path parameters besides [locale] in due time. Currently, these functionalities are yet to be developed.

Answer №1

The absence of autocomplete functionality is due to the structure of the routes, which are in the format /[locale]/rest/of/route rather than /en-US/rest/of/route. Consequently, the parameter types are not being inferred correctly.

What we need to do instead is define a type that can infer the path following /[locale] and apply that type to our path parameter.

type Route = "api/" | "/[locale]/" | "/[locale]/profile/welcome" | "/[locale]/profile/settings" | "/[locale]/users/posts"

// Implement a distributive conditional to exclude routes that do not begin with /[locale] such as /api
type RoutePastLocale = Route extends infer J ? J extends `/[locale]${infer I}` ? I : never : never;

export const SUPPORTED_LOCALES = ["en-US"] as const;

type Locale = typeof SUPPORTED_LOCALES[number]

function i18nPathname(locale: Locale, path: RoutePastLocale) {
  return `${locale}${path}`;
}

const home = i18nPathname('en-US', '/'); // Accurate autocomplete on path

Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I keep encountering an error with the Next.js Image component, even though I have properly included the src prop

By passing the src prop to the UserCard component, I am encountering the following error message. Error Unhandled Runtime Error Error: Image is missing required "src" property. Make sure you pass "src" in props to the `next/image` comp ...

Tips for positioning buttons within a responsive navigation bar

In my code for the navigation bar, located in "Navbar2.jsx", I have included various components and styles to create a dynamic navbar with dropdown functionality. The issue I am facing is that when the menu drops down on mobile view, it shifts the position ...

Exploring Formik, React-Select, and Managing Multiple State Variables

I'm completely stuck trying to solve this issue. I have an existing select component that works with formik and react-select, but I'm struggling to make it work for multiple selections. Here's my current setup: import Select from 'react ...

Personalize React-responsive-carousel arrow icons

In my Next JS app using react responsive carousel and tailwind, I want to customize the next and prev buttons to be circular. How can I achieve this? <Carousel axis="horizontal" showStatus={false}> <div className="w-full r ...

Issue with Dynamic Links in Content Management System: The error message "redirect" cannot be returned during prerendering in getStaticProps

I have a unique problem with my Next JS app that is connected to a CMS and hosted on Vercel. All the links are dynamic, created by content authors, and I'm trying to implement dynamic redirects to improve SEO. Specifically, I want to enforce lowercase ...

Error: Unexpected character U found at the beginning of the JSON data when using JSON.parse in Angular 8

Lately, I came across an issue while making changes to some parts of my previous code. The error caught my attention as it occurred when trying to pass a specific object or a part of that object in Angular 8, NodeJS with express, and mongoose. Upon checki ...

Tips for adding a new element to an array based on its unique identifier during the mapping process

Currently, I am dealing with a UI table populated by a GET request. My goal is to enhance each row by adding a new value fetched from another GET request. However, I'm facing an issue where I can't execute a request within a map function... expo ...

TypeScript encounters a self-referencing type alias circularly

Encountering an issue with Typescript 3.6.3, specifically getting the error: Type alias 'JSONValue' circularly references itself. View code online here In need of assistance to resolve the circular reference in this specific version of TS (note ...

Guide to implementing server-side data loading in App.js using Next.js

Is there a way to fetch the layout data along with the page without adding it to every individual page? The issue I'm facing is that my layout component, loaded once on App.jsx, remains consistent throughout the session. However, due to SSRed page loa ...

This element is not suitable for use as a JSX component since its return type 'void' is not a valid JSX element. Please check the return type to ensure it is compatible with

I have been working on this code snippet: function searchData(searchWord: any) { if (originalData.length > 0) { if (searchWord !== "") { setDataList([...originalData.filter((svc: any) => ...

Develop a TypeScript class by incorporating a static function from an external library, while ensuring type safety

I am looking to enhance the capabilities of the rxjs5 Observable class by adding a static function. While this can be easily accomplished in plain JavaScript: var myStaticFn = function() { /* ... */ }; Observable.myStaticFn = myStaticFn; this approach w ...

Tips on reusing the next-auth initFirestore object to retrieve additional information from Firestore

I have successfully set up a NextJS v13.4.2 project with next-auth v4.22.1 to enable users to authenticate using the 'Login with Google' button. Additionally, I am utilizing the next-auth Firebase adapter in my current setup. The relevant configu ...

An issue arose during runtime: ReferenceError is triggered on Safari as it cannot locate the variable ResizeObserver

I have incorporated a slider into my Next.js website, which can be found at https://github.com/nerdyman/react-compare-slider Everything functions perfectly except on Safari, both the desktop and iPhone versions. You can view my website here: The issue ar ...

How to import an HTML file using TypeScript

I need to load an html file located in the same directory as the typescript file and return it from the function. public ...(... ) : angular.IHttpPromise<string> { ... return $http({ method: 'GET', url: &apos ...

The 'formGroup' property cannot be bound as it is not recognized as a valid property of 'form' in Angular 7

I tried implementing a login feature using web API with Angular 7, but encountered an error when using <form [formGroup]="loginForm" (submit)="loginFormSubmit()">. What could be causing this issue? https://i.sstatic.net/3M2a5.jpg login.component.ht ...

Issues with the File plugin in Ionic 2

Currently, I am working on integrating Quickblox into my Ionic2 application. I have successfully implemented all the required functionalities except for file uploading. In Quickblox, there is a specific function that needs to be used for uploading files, a ...

Vuejs class-based components using typescript face limitations when it comes to updating data from an external API

Currently, I am utilizing VueJS alongside Typescript and class-based components. In my code, I encountered a warning that states: [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Inst ...

The function this.client.defaultMutationOptions does not exist in the react-query, pocketbase, and nextjs libraries

I am currently following a tutorial on using Pocketbase with NextJS, but the video I'm watching uses create-react-app. This means I have to make some adjustments as I go along. It has been an interesting experience, but I have hit a roadblock and my u ...

Discovering Constraints on File Size: API Response Encoded in Base64 Visible Within 1 Megabyte, Overcoming Rendering Obstacles

Currently, I am encountering a hurdle in my Next.js application when trying to upload images larger than 1 MB. While the app works seamlessly with smaller images, it faces challenges when dealing with larger ones. Initially, there was a browser console er ...

What is the best way to disable a collapsed section in VS Code using comments?

I'm wondering how to properly comment out a "folded" section of code using VS Code. For instance, I want to comment out the collapsible region: if (a == b) { dance(); } I am familiar with the keyboard shortcut for folding regions: Ctrl + Shift + ...