<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ikram Ul Haq</title>
    <description>The latest articles on DEV Community by Ikram Ul Haq (@ikramdeveloper).</description>
    <link>https://dev.to/ikramdeveloper</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F862423%2F4535e01d-eeb5-4048-adf5-774a9c25147d.jpeg</url>
      <title>DEV Community: Ikram Ul Haq</title>
      <link>https://dev.to/ikramdeveloper</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ikramdeveloper"/>
    <language>en</language>
    <item>
      <title>HTTP 500 on AWS Amplify After Deployment — Solved by a Monorepo Dependency Mismatch</title>
      <dc:creator>Ikram Ul Haq</dc:creator>
      <pubDate>Thu, 12 Mar 2026 16:20:16 +0000</pubDate>
      <link>https://dev.to/ikramdeveloper/http-500-on-aws-amplify-after-deployment-solved-by-a-monorepo-dependency-mismatch-4gfo</link>
      <guid>https://dev.to/ikramdeveloper/http-500-on-aws-amplify-after-deployment-solved-by-a-monorepo-dependency-mismatch-4gfo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I used Claude Code to investigate this issue. What could have taken a day of back-and-forth debugging was resolved in a focused session — pinpointing the root cause, testing fixes, and ruling out dead ends systematically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;After a successful deployment on AWS Amplify, the app was returning HTTP 500 with no build failure, no warning — just a blank page. CloudWatch logs showed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;ln: failed to create symbolic link '/tmp/app/node_modules/node_modules': Read-only file system
ln: failed to create symbolic link '/tmp/app/public/public': Read-only file system
ln: failed to create symbolic link '/tmp/app/.next/static/static': Read-only file system
ln: failed to create symbolic link '/tmp/app/.next/server/chunks/chunks': Read-only file system
INIT_REPORT Init Duration: 767.95 ms   Phase: invoke   Status: error   Error Type: Runtime.ExitError
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deployment was marked successful. The app was dead on arrival.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Next.js · pnpm monorepo · AWS Amplify SSR&lt;/p&gt;




&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;This is a pnpm monorepo with two apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app-1&lt;/code&gt; — already deployed and working on Amplify&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app-2&lt;/code&gt; — newly added to the monorepo, deployed in the same commit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both apps share the root &lt;code&gt;node_modules&lt;/code&gt; via pnpm hoisting.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Tried (All Failed)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;output: 'standalone'&lt;/code&gt; in &lt;code&gt;next.config.mjs&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The idea was to bundle all dependencies into &lt;code&gt;.next/standalone&lt;/code&gt; to avoid symlink resolution issues at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.mjs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same symlink errors in CloudWatch. Didn't help.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Scoped install in Amplify build spec
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;preBuild&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pnpm install --filter app-1...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intent was to install only &lt;code&gt;app-1&lt;/code&gt;'s dependency tree to avoid &lt;code&gt;app-2&lt;/code&gt;'s deps affecting the hoisting layout. Same error.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Removed &lt;code&gt;node_modules&lt;/code&gt; from Amplify cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.next/cache/**/*'&lt;/span&gt;
    &lt;span class="c1"&gt;# removed: node_modules/**/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ruled out stale cached &lt;code&gt;node_modules&lt;/code&gt;. Same error.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;code&gt;pnpm.overrides&lt;/code&gt; to force a single React version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"pnpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^19.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react-dom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^19.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was an attempt to eliminate the version conflict at the root. Same symlink error on Amplify.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Redeployed the last known-working commit
&lt;/h3&gt;

&lt;p&gt;Redeploying the commit before &lt;code&gt;app-2&lt;/code&gt; was added worked perfectly. This confirmed the issue was introduced entirely by &lt;code&gt;app-2&lt;/code&gt; being part of the monorepo — not an Amplify infrastructure issue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At this point we also found similar reports on the Amplify GitHub repo (&lt;a href="https://github.com/aws-amplify/amplify-hosting/issues/3793" rel="noopener noreferrer"&gt;#3793&lt;/a&gt;, &lt;a href="https://github.com/aws-amplify/amplify-hosting/issues/4079" rel="noopener noreferrer"&gt;#4079&lt;/a&gt;) with identical log output. Those were intermittent platform-side issues. Ours was consistent and reproducible — which pointed back to our code.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Twist: A Local Build Failure Along the Way
&lt;/h2&gt;

&lt;p&gt;While trying &lt;code&gt;pnpm.overrides&lt;/code&gt;, the local build started failing with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot read properties of null (reading 'useContext')
Cannot read properties of null (reading 'useRef')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Occurring during prerendering of &lt;code&gt;/404&lt;/code&gt; and &lt;code&gt;/500&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; pnpm was hoisting &lt;code&gt;react@19.2.3&lt;/code&gt; at root but &lt;code&gt;react-dom@18.3.1&lt;/code&gt; at root (pulled in by a transitive dependency from &lt;code&gt;app-2&lt;/code&gt;). Two incompatible React instances in the same &lt;code&gt;node_modules&lt;/code&gt; — classic split brain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; After removing the overrides, regenerating &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; from scratch cleaned up the hoisting and resolved the local build. This was a separate issue from the Amplify problem but surfaced during the same investigation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Actual Root Cause
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;app-1&lt;/code&gt; and &lt;code&gt;app-2&lt;/code&gt; had different versions of React and Next.js:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;app-1&lt;/th&gt;
&lt;th&gt;app-2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;next&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15.1.11&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16.1.4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;react&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^19.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;19.2.3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;react-dom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^19.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;19.2.3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The version mismatch in Next.js was enough for pnpm to create a conflicting &lt;code&gt;node_modules&lt;/code&gt; layout across the two apps — which Amplify's Lambda runtime couldn't handle at bootstrap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Downgraded &lt;code&gt;app-2&lt;/code&gt;'s Next.js from &lt;code&gt;16.1.4&lt;/code&gt; to &lt;code&gt;15.1.11&lt;/code&gt; and aligned React/react-dom versions to match &lt;code&gt;app-1&lt;/code&gt;. Clean layout, no symlink failures, app running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why It Didn't Fail Locally or on Vercel
&lt;/h2&gt;

&lt;p&gt;To rule out our own code, we deployed the same commit to Vercel — it worked fine. Ran it locally — worked fine. Only Amplify was breaking.&lt;/p&gt;

&lt;p&gt;That's because Node.js and Vercel are both flexible with how they resolve modules at runtime. Amplify's Lambda compute is not — it expects a specific &lt;code&gt;node_modules&lt;/code&gt; layout and breaks silently at the build level but loudly at runtime when that layout doesn't match. - According to Claude 😊&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;If you're running a pnpm monorepo on Amplify SSR and seeing &lt;code&gt;Read-only file system&lt;/code&gt; symlink errors in CloudWatch with a successful build — &lt;strong&gt;check your dependency versions across all apps before debugging Amplify's infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keep React and Next.js versions consistent across all apps in the monorepo. pnpm's hoisting is sensitive to version conflicts, and Amplify's runtime is sensitive to pnpm's hoisting layout.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Added a comment on the related GitHub issue here: &lt;a href="https://github.com/aws-amplify/amplify-hosting/issues/4079" rel="noopener noreferrer"&gt;aws-amplify/amplify-hosting#4079&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>nextjs</category>
      <category>pnpm</category>
      <category>devops</category>
    </item>
    <item>
      <title>i18n with NextJS output export</title>
      <dc:creator>Ikram Ul Haq</dc:creator>
      <pubDate>Tue, 06 Feb 2024 20:44:52 +0000</pubDate>
      <link>https://dev.to/ikramdeveloper/i18n-with-nextjs-output-export-1pbc</link>
      <guid>https://dev.to/ikramdeveloper/i18n-with-nextjs-output-export-1pbc</guid>
      <description>&lt;p&gt;In this post, I'll guide you through the process of setting up &lt;strong&gt;internationalization (i18n)&lt;/strong&gt; in a &lt;strong&gt;Next.js&lt;/strong&gt; project. We'll explore how to integrate i18n with the page router and address the export output option, overcoming challenges where Next.js doesn't provide direct assistance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fibnkrkunwwn87o4l9exo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fibnkrkunwwn87o4l9exo.png" alt="Next.js does not support i18n with output export" width="667" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To begin, let's initialize a Next.js v14 project with TypeScript. Alternatively, you can opt for JavaScript; either choice works seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing NextJS Project
&lt;/h2&gt;

&lt;p&gt;Execute the following command to set up your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`npx create-next-app@latest`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the setup, you'll encounter several questions; respond based on your preferences. In my case, I opted for the page router and specified the src directory with TypeScript.&lt;/p&gt;

&lt;p&gt;Afterward, include the &lt;code&gt;output: "export"&lt;/code&gt; in the next.config.js file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffwmsy8pp3fiq4csz80d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffwmsy8pp3fiq4csz80d.png" alt="next configuration with output: " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, remove the &lt;code&gt;api&lt;/code&gt; folder from the &lt;code&gt;pages&lt;/code&gt; directory, as it's incompatible with the &lt;code&gt;output: "export"&lt;/code&gt; configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing and Configuring i18n
&lt;/h2&gt;

&lt;p&gt;For internationalization, we'll be utilizing the &lt;a href="https://www.npmjs.com/package/next-translate" rel="noopener noreferrer"&gt;next-translate&lt;/a&gt; package. Although it doesn't inherently support output: export, don't fret – that's where our assistance comes in.&lt;/p&gt;

&lt;p&gt;Begin by installing the &lt;code&gt;next-translate&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`npm i next-translate`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a file named &lt;code&gt;i18n.ts&lt;/code&gt; or &lt;code&gt;.js&lt;/code&gt; at the project's root level, and insert the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { I18nConfig } from "next-translate";

export const i18nConfig = {
  locales: ["en", "es"],
  defaultLocale: "en",
  loader: false,
  pages: {
    "*": ["common"],
  },
  defaultNS: "common",
} satisfies I18nConfig;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've established the fundamental configuration for i18n. Feel free to customize it based on your specific needs.&lt;/p&gt;

&lt;p&gt;Now, establish a &lt;code&gt;locales&lt;/code&gt; folder at the project's root level. This folder will house translation files for each language.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd3m1r3xvj6jfr9tq1o5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd3m1r3xvj6jfr9tq1o5.png" alt="locales folder structure" width="656" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the &lt;code&gt;_app.ts&lt;/code&gt; file within the &lt;code&gt;pages&lt;/code&gt; directory, and envelop the &lt;code&gt;Component&lt;/code&gt; with &lt;code&gt;I18nProvider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "@/styles/globals.css";
import type { AppProps } from "next/app";
import I18nProvider from "next-translate/I18nProvider";
import { i18nConfig } from "../../i18n";
import commonES from "../../locales/es/common.json";
import commonEN from "../../locales/en/common.json";

const App = ({ Component, pageProps, router }: AppProps) =&amp;gt; {
  const lang = i18nConfig.locales.includes(router.query.locale as string)
    ? String(router.query.locale)
    : i18nConfig.defaultLocale;

  return (
    &amp;lt;I18nProvider
      lang={lang}
      namespaces={{ common: lang === "es" ? commonES : commonEN }}
    &amp;gt;
      &amp;lt;Component {...pageProps} /&amp;gt;
    &amp;lt;/I18nProvider&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We enclosed the &lt;code&gt;Component&lt;/code&gt; with &lt;code&gt;I18nProvider&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Detected the language from the route and passed it as a prop to &lt;code&gt;I18nProvider&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Defined the namespace file based on the current language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, generate a custom hook named &lt;code&gt;useI18n.ts&lt;/code&gt; in the &lt;code&gt;src/hooks&lt;/code&gt; directory and insert the code there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import useTranslation from "next-translate/useTranslation";
import { i18nConfig } from "../../i18n";

interface IUseI18n {
  namespace?: string;
}

export const useI18n = ({ namespace }: IUseI18n = {}) =&amp;gt; {
  const { t } = useTranslation(namespace ? namespace : i18nConfig.defaultNS);

  return { t };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hook will supply us with translations based on the provided namespace or the default namespace.&lt;/p&gt;

&lt;p&gt;Now, proceed to the &lt;code&gt;index.tsx&lt;/code&gt; file in the &lt;code&gt;pages&lt;/code&gt; directory and include the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useI18n } from "@/hooks/useI18n";

export default function Home() {
  const { t } = useI18n();
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{t("greeting")}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will display something similar to the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0x5w90losm7f8ozuzas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0x5w90losm7f8ozuzas.png" alt="Hello World" width="273" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hurrah! We've successfully configured i18n in our project. However, the language detection aspect is still pending. Let's implement that part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language Detection
&lt;/h2&gt;

&lt;p&gt;As observed earlier, we were fetching the locale value from &lt;code&gt;router.query&lt;/code&gt;. To proceed, establish a folder named &lt;code&gt;[locale]&lt;/code&gt; within the &lt;code&gt;pages&lt;/code&gt; directory. Note that all our route files will be contained within this folder. Create an &lt;code&gt;index.tsx&lt;/code&gt; file in the &lt;code&gt;[locale]&lt;/code&gt; folder and insert the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useI18n } from "@/hooks/useI18n";

const Home = () =&amp;gt; {
  const { t } = useI18n();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{t("greeting")}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Home;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, visiting &lt;code&gt;localhost:3000/en&lt;/code&gt; will display content in English, and changing it to &lt;code&gt;localhost:3000/es&lt;/code&gt; will show content in Spanish. This functionality also seamlessly integrates with dynamic routes.&lt;/p&gt;

&lt;p&gt;The structure of your &lt;code&gt;pages&lt;/code&gt; folder will resemble this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39yib43wqcbuoymyrt4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39yib43wqcbuoymyrt4q.png" alt="Pages folder structure with locale folder" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's implement language detection using &lt;a href="https://www.npmjs.com/package/next-language-detector" rel="noopener noreferrer"&gt;next-language-detector&lt;/a&gt; to cache our chosen language.&lt;/p&gt;

&lt;p&gt;Begin by installing &lt;code&gt;next-language-detector&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i next-language-detector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a folder named &lt;code&gt;lib&lt;/code&gt; in the &lt;code&gt;src&lt;/code&gt; directory, and within it, generate a file named &lt;code&gt;languageDetector.ts&lt;/code&gt;. Open the file and insert the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import nextLanguageDetector from "next-language-detector";
import { i18nConfig } from "../../i18n";

export const languageDetector = nextLanguageDetector({
  supportedLngs: i18nConfig.locales,
  fallbackLng: i18nConfig.defaultLocale,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate another file named &lt;code&gt;redirect.tsx&lt;/code&gt; in the same &lt;code&gt;lib&lt;/code&gt; folder and insert the following code there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useRouter } from "next/router";
import { useEffect } from "react";
import { languageDetector } from "./languageDetector";

export const useRedirect = (to?: string) =&amp;gt; {
  const router = useRouter();
  const redirectPath = to || router.asPath;

  // language detection
  useEffect(() =&amp;gt; {
    const detectedLng = languageDetector.detect();
    if (redirectPath.startsWith("/" + detectedLng) &amp;amp;&amp;amp; router.route === "/404") {
      // prevent endless loop
      router.replace("/" + detectedLng + router.route);
      return;
    }

    if (detectedLng &amp;amp;&amp;amp; languageDetector.cache) {
      languageDetector.cache(detectedLng);
    }
    router.replace("/" + detectedLng + redirectPath);
  });

  return &amp;lt;&amp;gt;&amp;lt;/&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, open the &lt;code&gt;index.tsx&lt;/code&gt; file in the &lt;code&gt;pages&lt;/code&gt; directory (outside the &lt;code&gt;[locale]&lt;/code&gt; folder) and replace the existing code with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useRedirect } from "@/lib/redirect";

const Redirect = () =&amp;gt; {
  useRedirect();
  return &amp;lt;&amp;gt;&amp;lt;/&amp;gt;;
};

export default Redirect;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anyone attempts to access the home page without specifying a locale, they will now be redirected to the locale page.&lt;/p&gt;

&lt;p&gt;However, creating a redirection page for all routes within the &lt;code&gt;[locale]&lt;/code&gt; folder may not be the most efficient practice. To address this, let's create a language wrapper that redirects to the route with the language if a user tries to access a page without specifying a language.&lt;/p&gt;

&lt;p&gt;Start by creating a folder named &lt;code&gt;wrappers&lt;/code&gt; inside the &lt;code&gt;src&lt;/code&gt; directory. Within this folder, generate a file named &lt;code&gt;LanguageWrapper.tsx&lt;/code&gt; and insert the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ReactNode, useEffect } from "react";
import { useRouter } from "next/router";
import { languageDetector } from "@/lib/languageDetector";
import { i18nConfig } from "../../i18n";

interface LanguageWrapperProps {
  children: ReactNode;
}

export const LanguageWrapper = ({ children }: LanguageWrapperProps) =&amp;gt; {
  const router = useRouter();
  const detectedLng = languageDetector.detect();

  useEffect(() =&amp;gt; {
    const {
      query: { locale },
      asPath,
      isReady,
    } = router;

    // Check if the current route has accurate locale
    if (isReady &amp;amp;&amp;amp; !i18nConfig.locales.includes(String(locale))) {
      if (asPath.startsWith("/" + detectedLng) &amp;amp;&amp;amp; router.route === "/404") {
        return;
      }

      if (detectedLng &amp;amp;&amp;amp; languageDetector.cache) {
        languageDetector.cache(detectedLng);
      }
      router.replace("/" + detectedLng + asPath);
    }
  }, [router, detectedLng]);

  return (router.query.locale &amp;amp;&amp;amp;
    i18nConfig.locales.includes(String(router.query.locale))) ||
    router.asPath.includes(detectedLng ?? i18nConfig.defaultLocale) ? (
    &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;
  ) : (
    &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add the LanguageWrapper in your _app.tsx file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29bc07vta3ak6cnooiig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29bc07vta3ak6cnooiig.png" alt="_app.tsx file with LanguageWrapper" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're almost done, just a few touches left. Let's create a component named &lt;code&gt;Link.tsx&lt;/code&gt; in the &lt;code&gt;src/components/_shared&lt;/code&gt; directory and insert the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ReactNode } from "react";
import NextLink from "next/link";
import { useRouter } from "next/router";

interface LinkProps {
  children: ReactNode;
  skipLocaleHandling?: boolean;
  locale?: string;
  href: string;
  target?: string;
}

export const Link = ({
  children,
  skipLocaleHandling,
  target,
  ...rest
}: LinkProps) =&amp;gt; {
  const router = useRouter();
  const locale = rest.locale || (router.query.locale as string) || "";

  let href = rest.href || router.asPath;
  if (href.indexOf("http") === 0) skipLocaleHandling = true;
  if (locale &amp;amp;&amp;amp; !skipLocaleHandling) {
    href = href
      ? `/${locale}${href}`
      : router.pathname.replace("[locale]", locale);
  }

  return (
    &amp;lt;NextLink href={href} target={target}&amp;gt;
      {children}
    &amp;lt;/NextLink&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will utilize this &lt;code&gt;Link&lt;/code&gt; component throughout our project instead of next/link component.&lt;/p&gt;

&lt;p&gt;Now, create a file named &lt;code&gt;useRouteRedirect.ts&lt;/code&gt; in the &lt;code&gt;hooks&lt;/code&gt; folder and insert the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useRouter } from "next/router";
import { i18nConfig } from "../../i18n";
import { languageDetector } from "@/lib/languageDetector";

export const useRouteRedirect = () =&amp;gt; {
  const router = useRouter();

  const redirect = (to: string, replace?: boolean) =&amp;gt; {
    const detectedLng = i18nConfig.locales.includes(String(router.query.locale))
      ? String(router.query.locale)
      : languageDetector.detect();
    if (to.startsWith("/" + detectedLng) &amp;amp;&amp;amp; router.route === "/404") {
      // prevent endless loop
      router.replace("/" + detectedLng + router.route);
      return;
    }

    if (detectedLng &amp;amp;&amp;amp; languageDetector.cache) {
      languageDetector.cache(detectedLng);
    }
    if (replace) {
      router.replace("/" + detectedLng + to);
    } else {
      router.push("/" + detectedLng + to);
    }
  };

  return { redirect };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use this custom hook instead of &lt;code&gt;router.push&lt;/code&gt; and &lt;code&gt;router.replace&lt;/code&gt; as illustrated below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fence2oltrv1xrk67ig8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fence2oltrv1xrk67ig8v.png" alt="Usage of useRouteRedirect hook" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, create a &lt;code&gt;LanguageSwitcher.tsx&lt;/code&gt; component in the &lt;code&gt;src/components&lt;/code&gt; directory to switch to a specific language with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { languageDetector } from "@/lib/languageDetector";
import { useRouter } from "next/router";
import Link from "next/link";

interface LanguageSwitcherProps {
  locale: string;
  href?: string;
  asPath?: string;
}

export const LanguageSwitcher = ({
  locale,
  ...rest
}: LanguageSwitcherProps) =&amp;gt; {
  const router = useRouter();

  let href = rest.href || router.asPath;
  let pName = router.pathname;
  Object.keys(router.query).forEach((k) =&amp;gt; {
    if (k === "locale") {
      pName = pName.replace(`[${k}]`, locale);
      return;
    }
    pName = pName.replace(`[${k}]`, String(router.query[k]));
  });
  if (locale) {
    href = rest.href ? `/${locale}${rest.href}` : pName;
  }

  return (
    &amp;lt;Link
      href={href}
      onClick={() =&amp;gt;
        languageDetector.cache ? languageDetector.cache(locale) : {}
      }
    &amp;gt;
      &amp;lt;button style={{ fontSize: "small" }}&amp;gt;{locale}&amp;lt;/button&amp;gt;
    &amp;lt;/Link&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! You have successfully implemented i18n in your Next.js project with &lt;code&gt;output: export&lt;/code&gt;. Keep in mind that if you want to load translations from different namespaces, like the 'dynamic' namespace, you'll need to define the namespaces and their translation files in &lt;code&gt;_app.tsx&lt;/code&gt; within the &lt;code&gt;I18nProvider&lt;/code&gt; component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw68zgdlq0mmqsfdawaax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw68zgdlq0mmqsfdawaax.png" alt="Loading namespace files according to the current language" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before testing it after creating a build, first, create a &lt;code&gt;404.tsx&lt;/code&gt; file in the &lt;code&gt;pages&lt;/code&gt; directory with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { FC, useEffect, useState } from "react";
import { NextRouter, useRouter } from "next/router";
import { getRouteRegex } from "next/dist/shared/lib/router/utils/route-regex";
import { getClientBuildManifest } from "next/dist/client/route-loader";
import { parseRelativeUrl } from "next/dist/shared/lib/router/utils/parse-relative-url";
import { isDynamicRoute } from "next/dist/shared/lib/router/utils/is-dynamic";
import { removeTrailingSlash } from "next/dist/shared/lib/router/utils/remove-trailing-slash";
import { Link } from "@/components/_shared/Link";

async function getPageList() {
  if (process.env.NODE_ENV === "production") {
    const { sortedPages } = await getClientBuildManifest();
    return sortedPages;
  } else {
    if (typeof window !== "undefined" &amp;amp;&amp;amp; window.__BUILD_MANIFEST?.sortedPages) {
      console.log(window.__BUILD_MANIFEST.sortedPages);
      return window.__BUILD_MANIFEST.sortedPages;
    }
  }
  return [];
}

async function getDoesLocationMatchPage(location: string) {
  const pages = await getPageList();

  let parsed = parseRelativeUrl(location);
  let { pathname } = parsed;
  return pathMatchesPage(pathname, pages);
}

function pathMatchesPage(pathname: string, pages: string[]) {
  const cleanPathname = removeTrailingSlash(pathname);

  if (pages.includes(cleanPathname)) {
    return true;
  }

  const page = pages.find(
    (page) =&amp;gt; isDynamicRoute(page) &amp;amp;&amp;amp; getRouteRegex(page).re.test(cleanPathname)
  );

  if (page) {
    return true;
  }
  return false;
}

/**
 * If both asPath and pathname are equal then it means that we
 * are on the correct route it still doesnt exist
 */
function doesNeedsProcessing(router: NextRouter) {
  const status = router.pathname !== router.asPath;
  console.log("Does Needs Processing", router.asPath, status);
  return status;
}

const Custom404 = () =&amp;gt; {
  const router = useRouter();

  const [isNotFound, setIsNotFound] = useState(false);

  const processLocationAndRedirect = async (router: NextRouter) =&amp;gt; {
    if (doesNeedsProcessing(router)) {
      const targetIsValidPage = await getDoesLocationMatchPage(router.asPath);
      if (targetIsValidPage) {
        await router.replace(router.asPath);
        return;
      }
    }
    setIsNotFound(true);
  };

  useEffect(() =&amp;gt; {
    if (router.isReady) {
      processLocationAndRedirect(router);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [router.isReady]);

  if (!isNotFound) return null;

  return (
    &amp;lt;div className="fixed inset-0 flex justify-center items-center"&amp;gt;
      &amp;lt;div className="flex flex-col gap-10"&amp;gt;
        &amp;lt;h1&amp;gt;Custom 404 - Page Not Found&amp;lt;/h1&amp;gt;
        &amp;lt;Link href="/"&amp;gt;
          &amp;lt;button&amp;gt;Go to Home Page&amp;lt;/button&amp;gt;
        &amp;lt;/Link&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Custom404;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To resolve page not found errors for dynamic routes, the inclusion of a &lt;code&gt;404.tsx&lt;/code&gt; file in the &lt;code&gt;pages&lt;/code&gt; directory is essential.&lt;/p&gt;

&lt;p&gt;Additionally, add the following command to your &lt;code&gt;package.json&lt;/code&gt; file to execute the code after creating the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"serve out/ -p 3000"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the &lt;code&gt;serve&lt;/code&gt; package is installed if it's not already present:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running &lt;code&gt;npm run build&lt;/code&gt; and then &lt;code&gt;npm run preview&lt;/code&gt;, you can access your project on port 3000.&lt;/p&gt;

&lt;p&gt;The complete code can be found on &lt;a href="https://github.com/ikramdeveloper/i18n-nextjs-output-export" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Some tweaks have been added there, so make sure to check it out.&lt;/p&gt;

&lt;p&gt;Feel free to comment if anything is missing or if you encounter any errors. I'm here to assist you. Thanks!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>i18n</category>
      <category>internationalization</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
