diff --git a/.changeset/config.json b/.changeset/config.json
index 538a20311..66c8227c7 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -8,5 +8,5 @@
   "baseBranch": "trunk",
   "updateInternalDependencies": "patch",
   "privatePackages": { "version": true, "tag": false },
-  "ignore": ["@10up/wp-nextjs", "@10up/wp-nextjs-app",  "@10up/wp-multisite-nextjs-app", "@10up/wp-multisite-nextjs", "@10up/wp-multisite-i18n-nextjs"]
+  "ignore": ["@10up/wp-nextjs", "@10up/wp-nextjs-app", "@10up/wp-polylang-nextjs-app",  "@10up/wp-multisite-nextjs-app", "@10up/wp-multisite-nextjs", "@10up/wp-multisite-i18n-nextjs"]
 }
\ No newline at end of file
diff --git a/.changeset/happy-squids-admire.md b/.changeset/happy-squids-admire.md
new file mode 100644
index 000000000..c2447593e
--- /dev/null
+++ b/.changeset/happy-squids-admire.md
@@ -0,0 +1,6 @@
+---
+"@headstartwp/core": minor
+"@headstartwp/next": minor
+---
+
+Adding support for `i18n` routing in app router
diff --git a/package-lock.json b/package-lock.json
index 57a69ddee..b0e0c5a5a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -114,6 +114,10 @@
       "resolved": "projects/wp-nextjs-app",
       "link": true
     },
+    "node_modules/@10up/wp-polylang-nextjs-app": {
+      "resolved": "projects/wp-polylang-nextjs-app",
+      "link": true
+    },
     "node_modules/@ampproject/remapping": {
       "version": "2.3.0",
       "license": "Apache-2.0",
@@ -2791,6 +2795,14 @@
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
+    "node_modules/@formatjs/intl-localematcher": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
+      "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
     "node_modules/@headstartwp/core": {
       "resolved": "packages/core",
       "link": true
@@ -15387,7 +15399,6 @@
     },
     "node_modules/negotiator": {
       "version": "0.6.3",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.6"
@@ -20950,7 +20961,7 @@
         "msw": "^0.35.0",
         "ts-jest": "^29.0.3",
         "tsc-esm-fix": "^2.20.27",
-        "typescript": "^5.4.5"
+        "typescript": "^5.5.3"
       },
       "peerDependencies": {
         "react": ">= 17.0.2"
@@ -20983,9 +20994,10 @@
       }
     },
     "packages/core/node_modules/typescript": {
-      "version": "5.4.5",
+      "version": "5.5.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
+      "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
       "dev": true,
-      "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -20999,10 +21011,12 @@
       "version": "1.4.3",
       "license": "MIT",
       "dependencies": {
+        "@formatjs/intl-localematcher": "^0.5.4",
         "@headstartwp/core": "^1.4.4",
         "@isaacs/ttlcache": "^1.4.1",
         "deepmerge": "^4.3.1",
         "loader-utils": "^3.2.0",
+        "negotiator": "^0.6.3",
         "schema-utils": "^4.0.0"
       },
       "devDependencies": {
@@ -21020,7 +21034,7 @@
         "node-mocks-http": "^1.14.1",
         "ts-jest": "^29.0.1",
         "tsc-esm-fix": "^2.20.27",
-        "typescript": "^5.4.5"
+        "typescript": "^5.5.3"
       },
       "peerDependencies": {
         "next": ">= 12.0.0",
@@ -21041,7 +21055,7 @@
         "ioredis-mock": "^8.9.0",
         "jest": "^29.0.3",
         "ts-jest": "^29.0.1",
-        "typescript": "^5.4.5"
+        "typescript": "^5.5.3"
       },
       "peerDependencies": {
         "next": ">= 13.2.0"
@@ -21053,9 +21067,10 @@
       "license": "MIT"
     },
     "packages/next-redis-cache-provider/node_modules/typescript": {
-      "version": "5.4.5",
+      "version": "5.5.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
+      "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
       "dev": true,
-      "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -21091,9 +21106,10 @@
       }
     },
     "packages/next/node_modules/typescript": {
-      "version": "5.4.5",
+      "version": "5.5.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
+      "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
       "dev": true,
-      "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -21312,7 +21328,7 @@
         "@types/react-dom": "^18",
         "@typescript-eslint/eslint-plugin": "^7.9.0",
         "@typescript-eslint/parser": "^7.9.0",
-        "typescript": "^5.4.5"
+        "typescript": "^5.5.3"
       },
       "engines": {
         "node": ">=18.0.0",
@@ -21545,9 +21561,135 @@
       }
     },
     "projects/wp-nextjs/node_modules/typescript": {
-      "version": "5.4.5",
+      "version": "5.5.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
+      "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "projects/wp-polylang-nextjs-app": {
+      "version": "0.1.0",
+      "dependencies": {
+        "@headstartwp/core": "^1.4.3",
+        "@headstartwp/next": "^1.4.2",
+        "next": "14.2.3",
+        "react": "^18",
+        "react-dom": "^18"
+      },
+      "devDependencies": {
+        "@10up/eslint-config": "^4.0.0",
+        "@types/node": "^20",
+        "@types/react": "^18",
+        "@types/react-dom": "^18",
+        "eslint": "^8",
+        "eslint-config-next": "14.2.3",
+        "typescript": "^5"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/env": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz",
+      "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA=="
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-linux-x64-gnu": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz",
+      "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-linux-x64-musl": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz",
+      "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@types/node": {
+      "version": "20.14.12",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
+      "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
+      "dev": true,
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/next": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz",
+      "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==",
+      "dependencies": {
+        "@next/env": "14.2.3",
+        "@swc/helpers": "0.5.5",
+        "busboy": "1.6.0",
+        "caniuse-lite": "^1.0.30001579",
+        "graceful-fs": "^4.2.11",
+        "postcss": "8.4.31",
+        "styled-jsx": "5.1.1"
+      },
+      "bin": {
+        "next": "dist/bin/next"
+      },
+      "engines": {
+        "node": ">=18.17.0"
+      },
+      "optionalDependencies": {
+        "@next/swc-darwin-arm64": "14.2.3",
+        "@next/swc-darwin-x64": "14.2.3",
+        "@next/swc-linux-arm64-gnu": "14.2.3",
+        "@next/swc-linux-arm64-musl": "14.2.3",
+        "@next/swc-linux-x64-gnu": "14.2.3",
+        "@next/swc-linux-x64-musl": "14.2.3",
+        "@next/swc-win32-arm64-msvc": "14.2.3",
+        "@next/swc-win32-ia32-msvc": "14.2.3",
+        "@next/swc-win32-x64-msvc": "14.2.3"
+      },
+      "peerDependencies": {
+        "@opentelemetry/api": "^1.1.0",
+        "@playwright/test": "^1.41.2",
+        "react": "^18.2.0",
+        "react-dom": "^18.2.0",
+        "sass": "^1.3.0"
+      },
+      "peerDependenciesMeta": {
+        "@opentelemetry/api": {
+          "optional": true
+        },
+        "@playwright/test": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        }
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/typescript": {
+      "version": "5.5.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+      "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
       "dev": true,
-      "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -21566,6 +21708,111 @@
       "engines": {
         "node": ">=16.0.0"
       }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-darwin-arm64": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz",
+      "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-darwin-x64": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz",
+      "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-linux-arm64-gnu": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz",
+      "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-linux-arm64-musl": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz",
+      "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-win32-arm64-msvc": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz",
+      "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-win32-ia32-msvc": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz",
+      "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "projects/wp-polylang-nextjs-app/node_modules/@next/swc-win32-x64-msvc": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz",
+      "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
     }
   }
 }
diff --git a/package.json b/package.json
index ecf8927b6..c297aab63 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,9 @@
     "dev": "turbo run dev --parallel --filter=./projects/wp-nextjs --filter=./packages/core --filter=./packages/next",
     "dev:app": "turbo run dev --parallel  --filter=./projects/wp-nextjs-app --filter=./packages/core --filter=./packages/next",
     "dev:app:multisite": "turbo run dev --parallel --filter=./projects/wp-multisite-nextjs-app --filter=./packages/core --filter=./packages/next",
+    "dev:app:polylang": "turbo run dev --parallel --filter=./projects/wp-polylang-nextjs-app --filter=./packages/core --filter=./packages/next",
     "dev:multisite": "turbo run dev --parallel --filter=./projects/wp-multisite-nextjs --filter=./packages/core --filter=./packages/next",
+    "dev:multisite:i18n": "turbo run dev --parallel --filter=./projects/wp-multisite-i18n-nextjs --filter=./packages/core --filter=./packages/next",
     "prepare": "husky install",
     "typedoc": "typedoc",
     "typedoc:watch": "typedoc --watch",
diff --git a/packages/core/package.json b/packages/core/package.json
index e18074e31..b0e6127c9 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -69,7 +69,7 @@
     "jest": "^29.3.1",
     "msw": "^0.35.0",
     "ts-jest": "^29.0.3",
-    "typescript": "^5.4.5",
+    "typescript": "^5.5.3",
     "isomorphic-fetch": "^3.0.0",
     "tsc-esm-fix": "^2.20.27",
     "@types/react": "^18",
diff --git a/packages/core/src/data/fetchFn/fetchAppSettings.ts b/packages/core/src/data/fetchFn/fetchAppSettings.ts
index 25ba99275..10fa5c1f7 100644
--- a/packages/core/src/data/fetchFn/fetchAppSettings.ts
+++ b/packages/core/src/data/fetchFn/fetchAppSettings.ts
@@ -4,7 +4,7 @@ import { getHeadstartWPConfig, getObjectProperty, getWPUrl } from '../../utils';
 import { AppEntity, MenuItemEntity } from '../types';
 import { QueryProps } from './types';
 
-export type AppQueryProps<P> = QueryProps<P> & {
+export type AppQueryProps<P extends EndpointParams> = QueryProps<P> & {
 	menu?: string;
 	blockSetting?: {
 		blockName?: string;
diff --git a/packages/core/src/data/fetchFn/types.ts b/packages/core/src/data/fetchFn/types.ts
index 2dd6a51d7..bf9e4153e 100644
--- a/packages/core/src/data/fetchFn/types.ts
+++ b/packages/core/src/data/fetchFn/types.ts
@@ -1,6 +1,6 @@
-import { FetchOptions } from '../strategies';
+import { EndpointParams, FetchOptions } from '../strategies';
 
-export type QueryProps<P> = {
+export type QueryProps<P extends EndpointParams> = {
 	path?: string;
 	params?: Partial<P>;
 	options?: Partial<FetchOptions>;
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index c9e97baa8..fac4a511b 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -137,6 +137,18 @@ export type FetchStrategyCacheConfig = {
 	cacheHandler?: FetchStrategyCacheHandler;
 };
 
+export type I18NConfig = {
+	locales: string[];
+	defaultLocale: string;
+
+	/**
+	 * Whether HeadstartWP should try to detect the browser preferred locale
+	 *
+	 * @default true
+	 */
+	localeDetection?: boolean;
+};
+
 export type HeadlessConfig = {
 	host?: string;
 	locale?: string;
@@ -149,7 +161,7 @@ export type HeadlessConfig = {
 	redirectStrategy?: RedirectStrategy;
 	useWordPressPlugin?: boolean;
 	integrations?: Integrations;
-	sites?: HeadlessConfig[];
+	i18n?: I18NConfig;
 	preview?: PreviewConfig;
 	debug?: {
 		requests?: boolean;
@@ -157,4 +169,5 @@ export type HeadlessConfig = {
 		devMode?: boolean;
 	};
 	cache?: FetchStrategyCacheConfig;
+	sites?: Array<Omit<HeadlessConfig, 'i18n'>>;
 };
diff --git a/packages/core/src/utils/config.ts b/packages/core/src/utils/config.ts
index 4846e9e62..0fa290091 100644
--- a/packages/core/src/utils/config.ts
+++ b/packages/core/src/utils/config.ts
@@ -32,6 +32,8 @@ export function getHeadstartWPConfig(): HeadlessConfig {
 		debug,
 		preview,
 		cache,
+		locale,
+		i18n,
 	} = __10up__HEADLESS_CONFIG;
 
 	const defaultTaxonomies: CustomTaxonomies = [
@@ -73,6 +75,7 @@ export function getHeadstartWPConfig(): HeadlessConfig {
 			: [...(customPostTypes || []), ...defaultPostTypes];
 
 	const headlessConfig = {
+		locale,
 		sourceUrl,
 		hostUrl: hostUrl || '',
 		customPostTypes: postTypes,
@@ -83,6 +86,7 @@ export function getHeadstartWPConfig(): HeadlessConfig {
 		debug,
 		preview,
 		cache,
+		i18n,
 		sites: (sites || []).map((site) => {
 			// if host is not defined but hostUrl is, infer host from hostUrl
 			if (typeof site.host === 'undefined' && typeof site.hostUrl !== 'undefined') {
diff --git a/packages/next-redis-cache-provider/package.json b/packages/next-redis-cache-provider/package.json
index 868494a5e..cbef6094d 100644
--- a/packages/next-redis-cache-provider/package.json
+++ b/packages/next-redis-cache-provider/package.json
@@ -32,7 +32,7 @@
         "@types/node": "^18.15.11",
         "jest": "^29.0.3",
         "ts-jest": "^29.0.1",
-        "typescript": "^5.4.5",
+        "typescript": "^5.5.3",
         "ioredis-mock": "^8.9.0",
         "@types/ioredis-mock": "^8.2.5"
     },
diff --git a/packages/next/package.json b/packages/next/package.json
index 3828c0ed7..5f1f24196 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -54,6 +54,8 @@
     "lint": "eslint src"
   },
   "dependencies": {
+    "negotiator": "^0.6.3",
+    "@formatjs/intl-localematcher": "^0.5.4",
     "deepmerge": "^4.3.1",
     "@headstartwp/core": "^1.4.4",
     "loader-utils": "^3.2.0",
@@ -70,7 +72,7 @@
     "next-router-mock": "^0.9.13",
     "node-mocks-http": "^1.14.1",
     "ts-jest": "^29.0.1",
-    "typescript": "^5.4.5",
+    "typescript": "^5.5.3",
     "isomorphic-fetch": "^3.0.0",
     "jest-fetch-mock": "^3.0.3",
     "tsc-esm-fix": "^2.20.27",
diff --git a/packages/next/src/config/withHeadstartWPConfig.ts b/packages/next/src/config/withHeadstartWPConfig.ts
index 8b4715462..23106075e 100644
--- a/packages/next/src/config/withHeadstartWPConfig.ts
+++ b/packages/next/src/config/withHeadstartWPConfig.ts
@@ -121,7 +121,7 @@ export function withHeadstartWPConfig(
 		}
 	});
 
-	return {
+	const config: NextConfig = {
 		...nextConfig,
 		images: {
 			...nextConfig.images,
@@ -285,6 +285,17 @@ export function withHeadstartWPConfig(
 			return config;
 		},
 	};
+
+	// if i18n is sets
+	// but we are on pages router
+	// error it out!
+	if ((headlessConfig.i18n?.locales?.length ?? 0) > 0 && !isUsingAppRouter) {
+		throw new ConfigError(
+			'The `i18n` option is not supported in the pages router. In the Pages router you must set the locales in the next config',
+		);
+	}
+
+	return config;
 }
 
 export function withHeadlessConfig(
diff --git a/packages/next/src/middlewares/__tests__/appMiddleware.ts b/packages/next/src/middlewares/__tests__/appMiddleware.ts
index 03c807296..670f8840d 100644
--- a/packages/next/src/middlewares/__tests__/appMiddleware.ts
+++ b/packages/next/src/middlewares/__tests__/appMiddleware.ts
@@ -1,6 +1,6 @@
 import { NextRequest } from 'next/server';
 import { setHeadstartWPConfig } from '@headstartwp/core/utils';
-import { AppMiddleware } from '../appMidleware';
+import { AppMiddleware, getAppRouterLocale } from '../appMidleware';
 
 describe('appMiddleware', () => {
 	it('adds headers', async () => {
@@ -110,6 +110,7 @@ describe('appMiddleware', () => {
 			'http://test2.com/_sites/test.com/post-name',
 		);
 		expect(res.headers.get('x-headstartwp-site')).toBe('test.com');
+		expect(res.headers.get('x-headstartwp-locale')).toBe('en');
 		expect(res.headers.get('x-headstartwp-current-url')).toBe('/post-name');
 	});
 
@@ -156,7 +157,7 @@ describe('appMiddleware', () => {
 			method: 'GET',
 			nextConfig: {
 				i18n: {
-					locales: ['zh', 'pr'],
+					locales: ['zh', 'pt-br'],
 					defaultLocale: 'pt-br',
 				},
 			},
@@ -167,7 +168,7 @@ describe('appMiddleware', () => {
 		await expect(() => AppMiddleware(req)).rejects.toThrow('Site not found.');
 	});
 
-	it('supports multisite in with App Router', async () => {
+	it('supports multisite with App Router', async () => {
 		setHeadstartWPConfig({
 			sites: [
 				{
@@ -196,7 +197,7 @@ describe('appMiddleware', () => {
 		expect(res.headers.get('x-headstartwp-current-url')).toBe('/post-name');
 	});
 
-	it('redirect /page/1 to page without 1', async () => {
+	it('redirect /page/1 to url without /page/1', async () => {
 		setHeadstartWPConfig({
 			sourceUrl: 'http://testwp.com',
 		});
@@ -248,4 +249,363 @@ describe('appMiddleware', () => {
 		res = await AppMiddleware(req);
 		expect(res.headers.get('x-headstartwp-current-url')).toBeNull();
 	});
+
+	it('[multisite] supports locales with App Router', async () => {
+		setHeadstartWPConfig({
+			i18n: {
+				defaultLocale: 'en',
+				locales: ['en', 'pt'],
+			},
+			sites: [
+				{
+					sourceUrl: 'http://testwp.com',
+					hostUrl: 'http://test.com',
+					locale: 'en',
+				},
+				{
+					sourceUrl: 'http://testwp2.com',
+					hostUrl: 'http://test2.com',
+					locale: 'pt',
+				},
+				{
+					sourceUrl: 'http://testwp2.com/en',
+					hostUrl: 'http://test2.com',
+					locale: 'en',
+				},
+			],
+		});
+
+		let req = new NextRequest('http://test2.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('host', 'test2.com');
+		req.headers.set('accept-language', 'pt-BR');
+
+		let res = await AppMiddleware(req, { appRouter: true });
+
+		expect(getAppRouterLocale(req)).toStrictEqual(['en', 'pt']);
+		expect(res.status).toBe(307);
+		expect(res.headers.get('Location')).toBe('http://test2.com/pt/post-name');
+
+		// follow the redirect
+		req = new NextRequest('http://test2.com/pt/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('host', 'test2.com');
+		req.headers.set('accept-language', 'pt-BR');
+
+		res = await AppMiddleware(req, { appRouter: true });
+
+		expect(res.headers.get('x-middleware-rewrite')).toBe(
+			'http://test2.com/pt/test2.com/post-name',
+		);
+		expect(res.headers.get('x-headstartwp-site')).toBe('test2.com');
+		expect(res.headers.get('x-headstartwp-locale')).toBe('pt');
+	});
+
+	it('[polylang] supports locales with app router', async () => {
+		setHeadstartWPConfig({
+			sourceUrl: 'http://testwp.com',
+			hostUrl: 'http://test.com',
+			integrations: {
+				polylang: {
+					enable: true,
+				},
+			},
+			i18n: {
+				locales: ['en', 'es'],
+				defaultLocale: 'en',
+			},
+		});
+
+		let req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'es');
+
+		expect(getAppRouterLocale(req)).toStrictEqual(['en', 'es']);
+
+		let res = await AppMiddleware(req, { appRouter: true });
+
+		expect(res.headers.get('x-headstartwp-locale')).toBe('es');
+		// it should redirect
+		expect(res.status).toBe(307);
+		expect(res.headers.get('Location')).toBe('http://test.com/es/post-name');
+
+		req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'en');
+
+		res = await AppMiddleware(req, { appRouter: true });
+		expect(res.headers.get('x-middleware-rewrite')).toBe('http://test.com/en/post-name');
+	});
+
+	it('[polylang no locale detection] supports locales with app router', async () => {
+		setHeadstartWPConfig({
+			sourceUrl: 'http://testwp.com',
+			hostUrl: 'http://test.com',
+			integrations: {
+				polylang: {
+					enable: true,
+				},
+			},
+			i18n: {
+				locales: ['en', 'es'],
+				defaultLocale: 'en',
+				localeDetection: false,
+			},
+		});
+
+		let req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'es');
+
+		expect(getAppRouterLocale(req)).toStrictEqual(['en', 'en']);
+
+		let res = await AppMiddleware(req, { appRouter: true });
+
+		expect(res.headers.get('x-headstartwp-locale')).toBe('en');
+		expect(res.status).toBe(200);
+
+		req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'en');
+
+		res = await AppMiddleware(req, { appRouter: true });
+		expect(res.headers.get('x-middleware-rewrite')).toBe('http://test.com/en/post-name');
+	});
+
+	it('[i18n] it does not cause redirect loops', async () => {
+		setHeadstartWPConfig({
+			sourceUrl: 'http://testwp.com',
+			hostUrl: 'http://test.com',
+			integrations: {
+				polylang: {
+					enable: true,
+				},
+			},
+			i18n: {
+				locales: ['en', 'es'],
+				defaultLocale: 'en',
+			},
+		});
+
+		let req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'es');
+
+		let res = await AppMiddleware(req, { appRouter: true });
+
+		expect(res.headers.get('x-headstartwp-locale')).toBe('es');
+		// it should redirect
+		expect(res.status).toBe(307);
+		expect(res.headers.get('Location')).toBe('http://test.com/es/post-name');
+
+		req = new NextRequest('http://test.com/es/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'es');
+
+		res = await AppMiddleware(req, { appRouter: true });
+		expect(res.status).toBe(200);
+
+		req = new NextRequest('http://test.com/en/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'en');
+
+		res = await AppMiddleware(req, { appRouter: true });
+		expect(res.headers.get('Location')).toBe('http://test.com/post-name');
+
+		req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'en');
+		res = await AppMiddleware(req, { appRouter: true });
+		expect(res.status).toBe(200);
+	});
+
+	it('[i18n] it skips locale detection if no locales array is set', async () => {
+		setHeadstartWPConfig({
+			sourceUrl: 'http://testwp.com',
+			hostUrl: 'http://test.com',
+			integrations: {
+				polylang: {
+					enable: true,
+				},
+			},
+			// @ts-expect-error
+			i18n: {
+				defaultLocale: 'en',
+			},
+		});
+
+		const req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('accept-language', 'es');
+
+		expect(getAppRouterLocale(req)).toBeUndefined();
+	});
+
+	it('throws on polylang and multisite together', async () => {
+		setHeadstartWPConfig({
+			sourceUrl: 'http://testwp.com',
+			hostUrl: 'http://test.com',
+			integrations: {
+				polylang: {
+					enable: true,
+				},
+			},
+			sites: [
+				{
+					sourceUrl: 'http://testwp.com',
+					hostUrl: 'http://test.com',
+					locale: 'en',
+				},
+				{
+					sourceUrl: 'http://testwp2.com',
+					hostUrl: 'http://test2.com',
+					locale: 'pt',
+				},
+				{
+					sourceUrl: 'http://testwp2.com/en',
+					hostUrl: 'http://test2.com',
+					locale: 'en',
+				},
+			],
+		});
+
+		const req = new NextRequest('http://test.com/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('host', 'test.com');
+		req.headers.set('accept-language', 'es');
+
+		expect(getAppRouterLocale(req)).toBeUndefined();
+
+		await expect(() => AppMiddleware(req, { appRouter: true })).rejects.toThrow(
+			'Polylang and multisite are not supported together',
+		);
+	});
+
+	it('[polylang] gets locale from url if set', async () => {
+		setHeadstartWPConfig({
+			sourceUrl: 'http://testwp.com',
+			hostUrl: 'http://test.com',
+			integrations: {
+				polylang: {
+					enable: true,
+				},
+			},
+			i18n: {
+				locales: ['en', 'es'],
+				defaultLocale: 'en',
+			},
+		});
+
+		let req = new NextRequest('http://test.com/en/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('host', 'test.com');
+		req.headers.set('accept-language', 'es');
+
+		expect(getAppRouterLocale(req)).toStrictEqual(['en', 'en']);
+		let res = await AppMiddleware(req, { appRouter: true });
+		expect(res.headers.get('x-headstartwp-locale')).toBe('en');
+		expect(res.headers.get('x-middleware-rewrite')).toBeNull();
+		// should redirect from /en/post-name to /post-name
+		expect(res.status).toBe(307);
+		expect(res.headers.get('Location')).toBe('http://test.com/post-name');
+
+		// pt is a unsuported but valid
+		// the it should not redirect but just let it 404
+		req = new NextRequest('http://test.com/pt/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('host', 'test.com');
+		req.headers.set('accept-language', 'es');
+
+		expect(getAppRouterLocale(req)).toStrictEqual(['en', 'pt']);
+		res = await AppMiddleware(req, { appRouter: true });
+		expect(res.headers.get('x-headstartwp-locale')).toBe('pt');
+		expect(res.headers.get('x-middleware-rewrite')).toBeNull();
+
+		expect(res.status).toBe(200);
+	});
+
+	it('[multisite with locale] gets locale from url if set', async () => {
+		setHeadstartWPConfig({
+			i18n: {
+				locales: ['en', 'es'],
+				defaultLocale: 'en',
+			},
+			sites: [
+				{
+					sourceUrl: 'http://testwp.com',
+					hostUrl: 'http://test.com',
+					locale: 'en',
+				},
+				{
+					sourceUrl: 'http://testwp2.com',
+					hostUrl: 'http://test2.com',
+					locale: 'pt',
+				},
+				{
+					sourceUrl: 'http://testwp2.com/en',
+					hostUrl: 'http://test2.com',
+					locale: 'en',
+				},
+			],
+		});
+
+		// request for test2.com with en locale so should match `http://testwp2.com/en as sourceUrl`
+		let req = new NextRequest('http://test2.com/en/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('host', 'test2.com');
+		// this locale should be skipped since it isn't supported and there's a locale in the URL
+		req.headers.set('accept-language', 'es');
+
+		expect(getAppRouterLocale(req)).toStrictEqual(['en', 'en']);
+		const res = await AppMiddleware(req, { appRouter: true });
+		expect(res.headers.get('x-headstartwp-locale')).toBe('en');
+		expect(res.headers.get('x-middleware-rewrite')).toBeNull();
+		// should redirect from /en/post-name to /post-name
+		expect(res.status).toBe(307);
+		expect(res.headers.get('Location')).toBe('http://test2.com/post-name');
+
+		// es is an unsuported but valid locale
+		req = new NextRequest('http://test2.com/es/post-name', {
+			method: 'GET',
+		});
+
+		req.headers.set('host', 'test2.com');
+		req.headers.set('accept-language', 'pt');
+
+		expect(getAppRouterLocale(req)).toStrictEqual(['en', 'es']);
+		// TODO: should we 404 instead?
+		await expect(() => AppMiddleware(req, { appRouter: true })).rejects.toThrow(
+			'Site not found',
+		);
+	});
 });
diff --git a/packages/next/src/middlewares/appMidleware.ts b/packages/next/src/middlewares/appMidleware.ts
index b8fdf1a75..df0b0e3d2 100644
--- a/packages/next/src/middlewares/appMidleware.ts
+++ b/packages/next/src/middlewares/appMidleware.ts
@@ -1,6 +1,8 @@
 import { NextResponse } from 'next/server';
 import type { NextRequest } from 'next/server';
 import { fetchRedirect, getHeadstartWPConfig, getSiteByHost } from '@headstartwp/core/utils';
+import Negotiator from 'negotiator';
+import { match as matchLocale } from '@formatjs/intl-localematcher';
 
 const ALLOWED_STATIC_PATHS = /^\/.*\.(ico|png|jpg|jpeg)$/g;
 
@@ -12,24 +14,113 @@ function isInternalRequest(req: NextRequest) {
 	return req.nextUrl.pathname.startsWith('/_next');
 }
 
+function hasMultisiteConfig() {
+	const config = getHeadstartWPConfig();
+	return (config.sites?.length ?? 0) > 0;
+}
+
+function isPolylangIntegrationEnabled() {
+	const config = getHeadstartWPConfig();
+	return config.integrations?.polylang?.enable ?? false;
+}
+
+function isValidLocale(locale: string) {
+	try {
+		return Intl.getCanonicalLocales(locale).length > 0;
+	} catch (e) {
+		return false;
+	}
+}
+
+function getAppRouterSupportedLocales() {
+	const config = getHeadstartWPConfig();
+
+	const { defaultLocale, locales = [], localeDetection = true } = config.i18n ?? {};
+
+	return {
+		defaultLocale,
+		supportedLocales: [...new Set(locales)],
+		localeDetection,
+	};
+}
+
+/**
+ * On App Router it returns a tuple with defaultLocale and locale
+ *
+ * @param request Next Request
+ * @returns A tuple [defaultLocale, locale]
+ */
+export function getAppRouterLocale(request: NextRequest): [string, string] | undefined {
+	const { defaultLocale, supportedLocales, localeDetection } = getAppRouterSupportedLocales();
+
+	if (typeof defaultLocale === 'undefined') {
+		return undefined;
+	}
+
+	if (supportedLocales.length === 0) {
+		return undefined;
+	}
+
+	// if there's a locale in the URL and it's a supported or valid locale, use it
+	const urlLocale = request.nextUrl.pathname.split('/')[1];
+	if (supportedLocales.includes(urlLocale) || isValidLocale(urlLocale)) {
+		return [defaultLocale, urlLocale];
+	}
+
+	if (localeDetection) {
+		// Negotiator expects plain object so we need to transform headers
+		const negotiatorHeaders: Record<string, string> = {};
+		request.headers.forEach((value, key) => {
+			negotiatorHeaders[key] = value;
+		});
+
+		const locales: readonly string[] = [defaultLocale, ...(supportedLocales ?? [])];
+
+		// Use negotiator and intl-localematcher to get best locale
+		const languages = new Negotiator({ headers: negotiatorHeaders }).languages(locales);
+
+		const locale = matchLocale(languages, locales, defaultLocale);
+
+		return [defaultLocale, locale];
+	}
+
+	return [defaultLocale, defaultLocale];
+}
+
 type AppMidlewareOptions = {
 	appRouter: boolean;
 };
 
 export async function AppMiddleware(
 	req: NextRequest,
-	// eslint-disable-next-line @typescript-eslint/no-unused-vars
 	options: AppMidlewareOptions = { appRouter: false },
 ) {
 	let response = NextResponse.next();
-	const currentUrl = req.nextUrl.pathname;
+	const { pathname } = req.nextUrl;
 
 	if (isStaticAssetRequest(req) || isInternalRequest(req)) {
 		return response;
 	}
 
+	const isPotentiallyMultisite = hasMultisiteConfig();
+	const hasPolylangIntegration = isPolylangIntegrationEnabled();
+
+	if (hasPolylangIntegration && isPotentiallyMultisite && options.appRouter) {
+		// potentially conflicting set up
+		// will figure out later if we need to support this
+		throw new Error('Polylang and multisite are not supported together');
+	}
+
+	const [defaultAppRouterLocale, appRouterLocale] = options.appRouter
+		? getAppRouterLocale(req) ?? []
+		: [];
+
+	const locale = options.appRouter && appRouterLocale ? appRouterLocale : req.nextUrl.locale;
+	const firstPathSlice = pathname.split('/')[1];
 	const hostname = req.headers.get('host') || '';
-	const site = getSiteByHost(hostname, req.nextUrl.locale);
+
+	// if it's polylang integration, we should not be using locale to get site
+	const site = getSiteByHost(hostname, !hasPolylangIntegration ? locale : undefined);
 	const isMultisiteRequest = site !== null && typeof site.sourceUrl !== 'undefined';
 
 	const {
@@ -39,11 +130,10 @@ export async function AppMiddleware(
 	} = isMultisiteRequest ? site : getHeadstartWPConfig();
 
 	if (!sourceUrl) {
+		// todo: should we 404 instead?
 		throw new Error('Site not found.');
 	}
 
-	const { pathname } = req.nextUrl;
-
 	if (redirectStrategy === 'always') {
 		const redirect = await fetchRedirect(pathname, sourceUrl || '');
 
@@ -55,25 +145,71 @@ export async function AppMiddleware(
 		}
 	}
 
-	if (req.nextUrl.pathname.endsWith('/page/1') || req.nextUrl.pathname.endsWith('/page/1/')) {
+	if (pathname.endsWith('/page/1') || pathname.endsWith('/page/1/')) {
 		return NextResponse.redirect(req.url.replace('/page/1', ''));
 	}
 
-	if (isMultisiteRequest) {
-		const url = req.nextUrl;
+	let shouldRedirect = false;
+
+	if (locale && options.appRouter) {
+		const { supportedLocales } = getAppRouterSupportedLocales();
+
+		const pathnameIsMissingLocale = supportedLocales.every(
+			(loc) => !pathname.startsWith(`/${loc}/`) && pathname !== `/${loc}`,
+		);
+
+		// redirect default locale in the URL to a version without the locale
+		// e.g /en/about-us -> /about-us where en is the default locale
+		if (locale === defaultAppRouterLocale && firstPathSlice === locale) {
+			shouldRedirect = true;
+			const pathNameWithoutLocale = pathname.replace(`/${locale}`, '');
+			response = NextResponse.redirect(
+				new URL(pathNameWithoutLocale, req.url.replace(`/${locale}`, '')),
+			);
+		}
+		// if we detected a non-default locale, there isn't a supported locale in the URL already
+		// but the first part of pathname (what is assumed to be a locale) is not a valid locale
+		// then we should redirect to add the locale
+		// e.g /about-us -> /en/about-us
+		else if (
+			locale !== defaultAppRouterLocale &&
+			pathnameIsMissingLocale &&
+			!isValidLocale(firstPathSlice)
+		) {
+			shouldRedirect = true;
+			response = NextResponse.redirect(
+				new URL(`/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`, req.url),
+			);
+		}
+		// nothing else and there's not a locale in path then rewrite to add default locale
+		else if (pathnameIsMissingLocale && !isValidLocale(firstPathSlice)) {
+			response = NextResponse.rewrite(
+				new URL(
+					`/${defaultAppRouterLocale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
+					req.url,
+				),
+			);
+		}
+	}
+
+	if (isMultisiteRequest && !shouldRedirect) {
+		const pagesRouterRewrite = `/_sites/${hostname}${pathname}`;
+		const appRouterRewrite = locale
+			? `/${locale}/${hostname}${pathname.replace(`/${locale}`, '')}`
+			: `/${hostname}${pathname}`;
 
 		response = NextResponse.rewrite(
-			new URL(
-				options.appRouter
-					? `/${hostname}${url.pathname}`
-					: `/_sites/${hostname}${url.pathname}`,
-				url,
-			),
+			new URL(options.appRouter ? appRouterRewrite : pagesRouterRewrite, req.nextUrl),
 		);
+
 		response.headers.set('x-headstartwp-site', hostname);
 	}
 
-	response.headers.set('x-headstartwp-current-url', currentUrl);
+	if (locale) {
+		response.headers.set('x-headstartwp-locale', locale);
+	}
+
+	response.headers.set('x-headstartwp-current-url', pathname);
 
 	return response;
 }
diff --git a/packages/next/src/rsc/data/queries/__tests__/prepareQuery.ts b/packages/next/src/rsc/data/queries/__tests__/prepareQuery.ts
index cbebfce97..5e1707cf9 100644
--- a/packages/next/src/rsc/data/queries/__tests__/prepareQuery.ts
+++ b/packages/next/src/rsc/data/queries/__tests__/prepareQuery.ts
@@ -1,4 +1,4 @@
-import { setHeadstartWPConfig } from '@headstartwp/core';
+import { getHeadstartWPConfig, setHeadstartWPConfig } from '@headstartwp/core';
 import { prepareQuery } from '../prepareQuery';
 
 describe('prepareQuery', () => {
@@ -82,3 +82,96 @@ describe('prepareQuery', () => {
 		).toThrow('Sub site not found, make sure to add site3.com to headstartwp.config.js');
 	});
 });
+
+describe('prepareQuery with lang and multisite', () => {
+	beforeAll(() => {
+		setHeadstartWPConfig({
+			sites: [
+				{
+					sourceUrl: 'https://backend1.com',
+					hostUrl: 'https://site1.com',
+					locale: 'en',
+				},
+				{
+					sourceUrl: 'https://backend2.com',
+					hostUrl: 'https://site1.com',
+					locale: 'pt',
+				},
+			],
+		});
+	});
+
+	it('gets site correctly based on lang and host', () => {
+		expect(
+			prepareQuery({
+				routeParams: { site: 'site1.com', lang: 'pt' },
+			}),
+		).toMatchObject({
+			config: {
+				sourceUrl: 'https://backend2.com',
+				hostUrl: 'https://site1.com',
+			},
+		});
+
+		expect(
+			prepareQuery({
+				routeParams: { site: 'site1.com', lang: 'en' },
+			}),
+		).toMatchObject({
+			config: {
+				sourceUrl: 'https://backend1.com',
+				hostUrl: 'https://site1.com',
+			},
+		});
+	});
+});
+
+describe('prepareQuery with lang and polylang', () => {
+	beforeAll(() => {
+		setHeadstartWPConfig({
+			sourceUrl: 'https://backend1.com',
+			hostUrl: 'https://site1.com',
+			integrations: {
+				polylang: {
+					enable: true,
+				},
+			},
+			i18n: {
+				locales: ['en', 'pt'],
+				defaultLocale: 'en',
+			},
+		});
+	});
+
+	it('prepares params correctly', () => {
+		expect(
+			prepareQuery(
+				{
+					routeParams: { lang: 'pt' },
+				},
+				getHeadstartWPConfig(),
+			),
+		).toMatchObject({
+			config: {
+				sourceUrl: 'https://backend1.com',
+				hostUrl: 'https://site1.com',
+			},
+			params: {
+				lang: 'pt',
+			},
+		});
+	});
+
+	it('throws for unsuported locales', () => {
+		expect(() =>
+			prepareQuery(
+				{
+					routeParams: { lang: 'br' },
+				},
+				getHeadstartWPConfig(),
+			),
+		).toThrow(
+			'Unsuported lang, make sure you add all desired locales to `config.i18n.locales`',
+		);
+	});
+});
diff --git a/packages/next/src/rsc/data/queries/prepareQuery.ts b/packages/next/src/rsc/data/queries/prepareQuery.ts
index cd6a68b48..9e1092ccb 100644
--- a/packages/next/src/rsc/data/queries/prepareQuery.ts
+++ b/packages/next/src/rsc/data/queries/prepareQuery.ts
@@ -1,4 +1,5 @@
 import {
+	EndpointParams,
 	FrameworkError,
 	HeadlessConfig,
 	getHeadstartWPConfig,
@@ -11,15 +12,23 @@ import type { NextQueryProps } from './types';
 
 const { all: merge } = deepmerge;
 
-export function prepareQuery<P>(
+export function prepareQuery<P extends EndpointParams>(
 	query: NextQueryProps<P>,
 	_config: HeadlessConfig | undefined = undefined,
 ) {
-	const { routeParams, handleError = true, ...rest } = query;
+	const { routeParams, handleError = true, params: originalParams, ...rest } = query;
 
 	const path = routeParams?.path ?? '';
+	const site = decodeURIComponent(routeParams?.site ?? '');
+
+	const rootConfig = getHeadstartWPConfig();
+	const isPolylangEnabled = rootConfig.integrations?.polylang?.enable;
+
+	// eslint-disable-next-line no-nested-ternary
 	const siteConfig = routeParams?.site
-		? getSiteByHost(decodeURIComponent(routeParams?.site))
+		? routeParams.lang && !isPolylangEnabled
+			? getSiteByHost(site, routeParams.lang)
+			: getSiteByHost(site)
 		: null;
 
 	if (routeParams?.site && !siteConfig) {
@@ -37,11 +46,25 @@ export function prepareQuery<P>(
 		rest.options ?? {},
 	]);
 
-	const config = siteConfig ?? _config;
+	const config = siteConfig ?? (_config || rootConfig);
 	const pathname = Array.isArray(path) ? convertToPath(path) : path;
 
+	const params: typeof originalParams =
+		typeof originalParams !== 'undefined' ? { ...originalParams } : {};
+
+	if (routeParams?.lang && isPolylangEnabled) {
+		const supportedLocales = rootConfig.i18n?.locales ?? [];
+		if (!supportedLocales.includes(routeParams.lang)) {
+			throw new FrameworkError(
+				'Unsuported lang, make sure you add all desired locales to `config.i18n.locales`',
+			);
+		}
+		params.lang = routeParams.lang;
+	}
+
 	return {
 		...rest,
+		params,
 		options,
 		path: pathname,
 		config: config ?? getHeadstartWPConfig(),
diff --git a/packages/next/src/rsc/data/queries/types.ts b/packages/next/src/rsc/data/queries/types.ts
index 41452dff8..120c75362 100644
--- a/packages/next/src/rsc/data/queries/types.ts
+++ b/packages/next/src/rsc/data/queries/types.ts
@@ -1,9 +1,10 @@
-import type { QueryProps } from '@headstartwp/core';
+import type { EndpointParams, QueryProps } from '@headstartwp/core';
 
-export type NextQueryProps<P> = {
+export type NextQueryProps<P extends EndpointParams> = {
 	routeParams?: {
 		path?: string | string[];
 		site?: string;
+		lang?: string;
 		[k: string]: unknown;
 	};
 	handleError?: boolean;
diff --git a/packages/next/src/rsc/types.ts b/packages/next/src/rsc/types.ts
index 7b4ebcbe0..e18dce76f 100644
--- a/packages/next/src/rsc/types.ts
+++ b/packages/next/src/rsc/types.ts
@@ -1,8 +1,8 @@
 export type HeadstartWPRoute<Params extends { [k: string]: unknown } = {}> = {
-	params: { path: string[]; site?: string };
+	params: { path: string[]; site?: string; lang?: string };
 } & Params;
 
 export type HeadstartWPLayout<Params extends { [k: string]: unknown } = {}> = {
-	params: { site?: string };
+	params: { site?: string; lang?: string };
 	children: React.ReactNode;
 } & Params;
diff --git a/projects/wp-multisite-i18n-nextjs/.env.trunk b/projects/wp-multisite-i18n-nextjs/.env.trunk
deleted file mode 100644
index e69de29bb..000000000
diff --git a/projects/wp-multisite-i18n-nextjs/babel.config.js b/projects/wp-multisite-i18n-nextjs/babel.config.js
deleted file mode 100644
index 2675b8c62..000000000
--- a/projects/wp-multisite-i18n-nextjs/babel.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
-	presets: ['next/babel'],
-};
diff --git a/projects/wp-multisite-i18n-nextjs/headless.config.js b/projects/wp-multisite-i18n-nextjs/headstartwp.config.js
similarity index 100%
rename from projects/wp-multisite-i18n-nextjs/headless.config.js
rename to projects/wp-multisite-i18n-nextjs/headstartwp.config.js
diff --git a/projects/wp-multisite-i18n-nextjs/next.config.js b/projects/wp-multisite-i18n-nextjs/next.config.js
index e30be1210..e481b81e0 100644
--- a/projects/wp-multisite-i18n-nextjs/next.config.js
+++ b/projects/wp-multisite-i18n-nextjs/next.config.js
@@ -1,11 +1,9 @@
-const { withHeadlessConfig } = require('@headstartwp/next/config');
+const { withHeadstartWPConfig } = require('@headstartwp/next/config');
 
 const withBundleAnalyzer = require('@next/bundle-analyzer')({
 	enabled: process.env.ANALYZE === 'true',
 });
 
-const headlessConfig = require('./headless.config');
-
 /**
  * Update whatever you need within the nextConfig object.
  *
@@ -22,4 +20,4 @@ const nextConfig = {
 	},
 };
 
-module.exports = withBundleAnalyzer(withHeadlessConfig(nextConfig, headlessConfig));
+module.exports = withBundleAnalyzer(withHeadstartWPConfig(nextConfig));
diff --git a/projects/wp-multisite-i18n-nextjs/src/components/Header/Nav.js b/projects/wp-multisite-i18n-nextjs/src/components/Header/Nav.js
index fcf19d9a7..7b281f805 100644
--- a/projects/wp-multisite-i18n-nextjs/src/components/Header/Nav.js
+++ b/projects/wp-multisite-i18n-nextjs/src/components/Header/Nav.js
@@ -33,12 +33,7 @@ const navStyles = css`
 `;
 
 export const Nav = () => {
-	const { data, loading, error } = useMenu('primary', {
-		// these settings will re-render menu client side to ensure
-		// it always have the latest items
-		revalidateOnMount: true,
-		revalidateOnFocus: true,
-	});
+	const { data, loading, error } = useMenu('primary');
 
 	if (loading || error) {
 		return null;
diff --git a/projects/wp-nextjs/package.json b/projects/wp-nextjs/package.json
index 6c2ed47c2..466d88b7b 100644
--- a/projects/wp-nextjs/package.json
+++ b/projects/wp-nextjs/package.json
@@ -33,7 +33,7 @@
     "@next/bundle-analyzer": "^12.1.0",
     "@typescript-eslint/eslint-plugin": "^7.9.0",
     "@typescript-eslint/parser": "^7.9.0",
-    "typescript": "^5.4.5",
+    "typescript": "^5.5.3",
     "@types/nprogress": "^0.2.3",
     "@types/react": "^18",
     "@types/react-dom": "^18"
diff --git a/projects/wp-polylang-nextjs-app/.env b/projects/wp-polylang-nextjs-app/.env
new file mode 100644
index 000000000..b013281ea
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/.env
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_HEADLESS_WP_URL=https://js1.10up.com
+NEXT_PUBLIC_HOST_URL=https://js1.10up.com
\ No newline at end of file
diff --git a/projects/wp-polylang-nextjs-app/.eslintrc.js b/projects/wp-polylang-nextjs-app/.eslintrc.js
new file mode 100644
index 000000000..16b1cc13e
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/.eslintrc.js
@@ -0,0 +1,14 @@
+module.exports = {
+	extends: ['@10up/eslint-config/react'],
+	parser: '@typescript-eslint/parser',
+	plugins: ['@typescript-eslint'],
+	settings: {
+		jsdoc: {
+			mode: 'typescript',
+		},
+	},
+	rules: {
+		'react/require-default-props': ['error', { functions: 'defaultArguments' }],
+		'jsdoc/require-returns-type': 'off',
+	},
+};
diff --git a/projects/wp-polylang-nextjs-app/.gitignore b/projects/wp-polylang-nextjs-app/.gitignore
new file mode 100644
index 000000000..fd3dbb571
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/projects/wp-polylang-nextjs-app/README.md b/projects/wp-polylang-nextjs-app/README.md
new file mode 100644
index 000000000..c4033664f
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/projects/wp-polylang-nextjs-app/headstartwp.config.js b/projects/wp-polylang-nextjs-app/headstartwp.config.js
new file mode 100644
index 000000000..3a23b5906
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/headstartwp.config.js
@@ -0,0 +1,22 @@
+/**
+ * Headless Config
+ *
+ * @type {import('@headstartwp/core').HeadlessConfig}
+ */
+module.exports = {
+	useWordPressPlugin: true,
+	sourceUrl: process.env.NEXT_PUBLIC_HEADLESS_WP_URL,
+	hostUrl: process.env.NEXT_PUBLIC_HOST_URL,
+	preview: {
+		usePostLinkForRedirect: true,
+	},
+	i18n: {
+		locales: ['en', 'pt', 'es'],
+		defaultLocale: 'en',
+	},
+	integrations: {
+		polylang: {
+			enable: true,
+		},
+	},
+};
diff --git a/projects/wp-polylang-nextjs-app/next.config.js b/projects/wp-polylang-nextjs-app/next.config.js
new file mode 100644
index 000000000..bf74d4ecd
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/next.config.js
@@ -0,0 +1,22 @@
+const { withHeadstartWPConfig } = require('@headstartwp/next/config');
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+	webpack: (config) => {
+		// TODO: figure out why this is needed
+		config.resolve = {
+			...config.resolve,
+			conditionNames: ['import'],
+		};
+
+		return config;
+	},
+
+	logging: {
+		fetches: {
+			fullUrl: true,
+		},
+	},
+};
+
+module.exports = withHeadstartWPConfig(nextConfig);
diff --git a/projects/wp-polylang-nextjs-app/package.json b/projects/wp-polylang-nextjs-app/package.json
new file mode 100644
index 000000000..9e01db510
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/package.json
@@ -0,0 +1,27 @@
+{
+  "name": "@10up/wp-polylang-nextjs-app",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "dev": "next dev",
+    "build": "next build",
+    "start": "next start",
+    "lint": "next lint"
+  },
+  "dependencies": {
+    "react": "^18",
+    "react-dom": "^18",
+    "next": "14.2.3",
+    "@headstartwp/core": "^1.4.3",
+    "@headstartwp/next": "^1.4.2"
+  },
+  "devDependencies": {
+    "@10up/eslint-config": "^4.0.0",
+    "typescript": "^5",
+    "@types/node": "^20",
+    "@types/react": "^18",
+    "@types/react-dom": "^18",
+    "eslint": "^8",
+    "eslint-config-next": "14.2.3"
+  }
+}
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/(single)/[...path]/page.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/(single)/[...path]/page.tsx
new file mode 100644
index 000000000..4fd359fe4
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/(single)/[...path]/page.tsx
@@ -0,0 +1,28 @@
+import { BlocksRenderer, HtmlDecoder } from '@headstartwp/core/react';
+import { HeadstartWPRoute, queryPost } from '@headstartwp/next/app';
+
+const Single = async ({ params }: HeadstartWPRoute) => {
+	const { data } = await queryPost({
+		routeParams: params,
+		params: {
+			postType: ['post', 'page'],
+		},
+		options: {
+			headers: {
+				cache: 'force-cache',
+			},
+		},
+	});
+
+	return (
+		<article>
+			<h1>
+				<HtmlDecoder html={data.post.title.rendered ?? ''} />
+			</h1>
+
+			<BlocksRenderer html={data.post.content.rendered ?? ''} />
+		</article>
+	);
+};
+
+export default Single;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/author/[...path]/page.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/author/[...path]/page.tsx
new file mode 100644
index 000000000..08eec52ea
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/author/[...path]/page.tsx
@@ -0,0 +1,24 @@
+import { HeadstartWPRoute, queryAuthorArchive } from '@headstartwp/next/app';
+import Link from 'next/link';
+
+const AuthorArchive = async ({ params }: HeadstartWPRoute) => {
+	const { data } = await queryAuthorArchive({
+		routeParams: params,
+	});
+
+	return (
+		<article>
+			<h1>{data.queriedObject.author?.name}</h1>
+
+			<ul>
+				{data.posts.map((post) => (
+					<li key={post.id}>
+						<Link href={post.link}>{post.title.rendered}</Link>
+					</li>
+				))}
+			</ul>
+		</article>
+	);
+};
+
+export default AuthorArchive;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/blog/[[...path]]/page.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/blog/[[...path]]/page.tsx
new file mode 100644
index 000000000..f4aa233e0
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/blog/[[...path]]/page.tsx
@@ -0,0 +1,61 @@
+import { PostEntity, QueriedObject } from '@headstartwp/core';
+import { HeadstartWPRoute, queryPostOrPosts } from '@headstartwp/next/app';
+import Link from 'next/link';
+import { notFound } from 'next/navigation';
+
+type ArchiveProps = {
+	posts: PostEntity[];
+	queriedObject: QueriedObject;
+};
+
+const Archive = ({ posts, queriedObject }: ArchiveProps) => {
+	return (
+		<main>
+			<h1>{queriedObject.term?.name}</h1>
+
+			<ul>
+				{posts.map((post) => (
+					<li key={post.id}>
+						<Link href={post.link}>{post.title.rendered}</Link>
+					</li>
+				))}
+			</ul>
+		</main>
+	);
+};
+
+const BlogPage = async ({ params }: HeadstartWPRoute) => {
+	const { isArchive, isSingle, data } = await queryPostOrPosts({
+		routeParams: params,
+		params: {
+			single: {
+				postType: 'post',
+			},
+			archive: {
+				postType: 'post',
+				/**
+				 * Specifying the _fields param reduces the amount of data queried and returned by the API.
+				 */
+				_fields: ['id', 'title', 'link'],
+			},
+			priority: 'single',
+			routeMatchStrategy: 'single',
+		},
+	});
+
+	if (isArchive && typeof data.posts !== 'undefined') {
+		return <Archive posts={data.posts} queriedObject={data.queriedObject} />;
+	}
+
+	if (isSingle && typeof data.post !== 'undefined') {
+		return (
+			<article>
+				<h1>{data.post.title.rendered}</h1>
+			</article>
+		);
+	}
+
+	return notFound();
+};
+
+export default BlogPage;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/category/[...path]/page.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/category/[...path]/page.tsx
new file mode 100644
index 000000000..8538d57f1
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/category/[...path]/page.tsx
@@ -0,0 +1,27 @@
+import { HeadstartWPRoute, queryPosts } from '@headstartwp/next/app';
+import Link from 'next/link';
+
+const CategoryArchive = async ({ params }: HeadstartWPRoute) => {
+	const { data } = await queryPosts({
+		routeParams: params,
+		params: {
+			taxonomy: 'category',
+		},
+	});
+
+	return (
+		<article>
+			<h1>{data.queriedObject.term?.name}</h1>
+
+			<ul>
+				{data.posts.map((post) => (
+					<li key={post.id}>
+						<Link href={post.link}>{post.title.rendered}</Link>
+					</li>
+				))}
+			</ul>
+		</article>
+	);
+};
+
+export default CategoryArchive;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/favicon.ico b/projects/wp-polylang-nextjs-app/src/app/[lang]/favicon.ico
new file mode 100644
index 000000000..718d6fea4
Binary files /dev/null and b/projects/wp-polylang-nextjs-app/src/app/[lang]/favicon.ico differ
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/globals.css b/projects/wp-polylang-nextjs-app/src/app/[lang]/globals.css
new file mode 100644
index 000000000..687196d61
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/globals.css
@@ -0,0 +1,34 @@
+.form-container {
+  position: fixed;
+  bottom: 20px;
+  right: 20px;
+  background-color: #f1f1f1;
+  padding: 20px;
+  border-radius: 5px;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+
+.form-container form {
+  display: flex;
+  flex-direction: column;
+}
+
+.form-container input {
+  margin-bottom: 10px;
+  padding: 10px;
+  border: 1px solid #ccc;
+  border-radius: 3px;
+}
+
+.form-container button {
+  padding: 10px;
+  background-color: #4CAF50;
+  color: white;
+  border: none;
+  border-radius: 3px;
+  cursor: pointer;
+}
+
+.form-container button:hover {
+  background-color: #45a049;
+}
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/layout.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/layout.tsx
new file mode 100644
index 000000000..0501c9c14
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/layout.tsx
@@ -0,0 +1,19 @@
+import { HeadstartWPLayout, PreviewIndicator, queryAppSettings } from '@headstartwp/next/app';
+import { Menu, SettingsProvider, ThemeSettingsProvider } from '@headstartwp/core/react';
+import { getHeadstartWPConfig } from '@headstartwp/core';
+
+const RootLayout = async ({ children, params }: Readonly<HeadstartWPLayout>) => {
+	const { menu, data } = await queryAppSettings({ menu: 'primary', routeParams: params });
+
+	return (
+		<ThemeSettingsProvider data={data['theme.json']}>
+			<SettingsProvider settings={getHeadstartWPConfig()}>
+				{menu ? <Menu items={menu} /> : null}
+				{children}
+				<PreviewIndicator className="form-container" />
+			</SettingsProvider>
+		</ThemeSettingsProvider>
+	);
+};
+
+export default RootLayout;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/not-found.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/not-found.tsx
new file mode 100644
index 000000000..0b6d8855c
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/not-found.tsx
@@ -0,0 +1,13 @@
+import Link from 'next/link';
+
+const NotFound = () => {
+	return (
+		<div>
+			<h2>Not Found</h2>
+			<p>Could not find requested resource</p>
+			<Link href="/">Return Home</Link>
+		</div>
+	);
+};
+
+export default NotFound;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/page.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/page.tsx
new file mode 100644
index 000000000..712e3efc7
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/page.tsx
@@ -0,0 +1,28 @@
+import { BlocksRenderer } from '@headstartwp/core/react';
+import { HeadstartWPRoute, queryAppSettings, queryPost } from '@headstartwp/next/app';
+
+const Home = async ({ params }: HeadstartWPRoute) => {
+	const {
+		data: { home },
+	} = await queryAppSettings({
+		routeParams: params,
+	});
+
+	const { data } = await queryPost({
+		routeParams: params,
+		params: {
+			slug: home.slug ?? 'front-page',
+			postType: 'page',
+		},
+	});
+
+	return (
+		<main>
+			<div>
+				<BlocksRenderer html={data.post.content.rendered ?? ''} />
+			</div>
+		</main>
+	);
+};
+
+export default Home;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/search/[[...path]]/page.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/search/[[...path]]/page.tsx
new file mode 100644
index 000000000..aa29ec914
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/search/[[...path]]/page.tsx
@@ -0,0 +1,29 @@
+import { HeadstartWPRoute, querySearch } from '@headstartwp/next/app';
+import Link from 'next/link';
+
+const Search = async ({ params }: HeadstartWPRoute) => {
+	const { data } = await querySearch({
+		routeParams: params,
+	});
+
+	if (data.pageInfo.totalItems === 0) {
+		return 'Nothing found';
+	}
+
+	return (
+		<>
+			<h1>Search Results</h1>
+			<ul>
+				{data.searchResults.map((item) => (
+					<li key={item.id}>
+						<Link href={item.url}>
+							{item.id} - {item.title}
+						</Link>
+					</li>
+				))}
+			</ul>
+		</>
+	);
+};
+
+export default Search;
diff --git a/projects/wp-polylang-nextjs-app/src/app/[lang]/tag/[...path]/page.tsx b/projects/wp-polylang-nextjs-app/src/app/[lang]/tag/[...path]/page.tsx
new file mode 100644
index 000000000..283091063
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/[lang]/tag/[...path]/page.tsx
@@ -0,0 +1,27 @@
+import { HeadstartWPRoute, queryPosts } from '@headstartwp/next/app';
+import Link from 'next/link';
+
+const TagArchive = async ({ params }: HeadstartWPRoute) => {
+	const { data } = await queryPosts({
+		routeParams: params,
+		params: {
+			taxonomy: 'post_tag',
+		},
+	});
+
+	return (
+		<article>
+			<h1>{data.queriedObject.term?.name}</h1>
+
+			<ul>
+				{data.posts.map((post) => (
+					<li key={post.id}>
+						<Link href={post.link}>{post.title.rendered}</Link>
+					</li>
+				))}
+			</ul>
+		</article>
+	);
+};
+
+export default TagArchive;
diff --git a/projects/wp-polylang-nextjs-app/src/app/api/preview/route.ts b/projects/wp-polylang-nextjs-app/src/app/api/preview/route.ts
new file mode 100644
index 000000000..ce26916d5
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/api/preview/route.ts
@@ -0,0 +1,7 @@
+import { previewRouteHandler } from '@headstartwp/next/app';
+import type { NextRequest } from 'next/server';
+
+export async function GET(request: NextRequest) {
+	// @ts-expect-error
+	return previewRouteHandler(request);
+}
diff --git a/projects/wp-polylang-nextjs-app/src/app/api/revalidate/route.ts b/projects/wp-polylang-nextjs-app/src/app/api/revalidate/route.ts
new file mode 100644
index 000000000..dc1dc3548
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/api/revalidate/route.ts
@@ -0,0 +1,7 @@
+import { revalidateRouteHandler } from '@headstartwp/next/app';
+import type { NextRequest } from 'next/server';
+
+export async function GET(request: NextRequest) {
+	// @ts-expect-error
+	return revalidateRouteHandler(request);
+}
diff --git a/projects/wp-polylang-nextjs-app/src/app/layout.tsx b/projects/wp-polylang-nextjs-app/src/app/layout.tsx
new file mode 100644
index 000000000..36d26353e
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/layout.tsx
@@ -0,0 +1,23 @@
+import type { Metadata } from 'next';
+import { Inter } from 'next/font/google';
+
+const inter = Inter({ subsets: ['latin'] });
+
+export const metadata: Metadata = {
+	title: 'Create Next App',
+	description: 'Generated by create next app',
+};
+
+const RootLayout = async ({
+	children,
+}: Readonly<{
+	children: React.ReactNode;
+}>) => {
+	return (
+		<html lang="en">
+			<body className={inter.className}>{children}</body>
+		</html>
+	);
+};
+
+export default RootLayout;
diff --git a/projects/wp-polylang-nextjs-app/src/app/not-found.tsx b/projects/wp-polylang-nextjs-app/src/app/not-found.tsx
new file mode 100644
index 000000000..0b6d8855c
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/app/not-found.tsx
@@ -0,0 +1,13 @@
+import Link from 'next/link';
+
+const NotFound = () => {
+	return (
+		<div>
+			<h2>Not Found</h2>
+			<p>Could not find requested resource</p>
+			<Link href="/">Return Home</Link>
+		</div>
+	);
+};
+
+export default NotFound;
diff --git a/projects/wp-polylang-nextjs-app/src/middleware.ts b/projects/wp-polylang-nextjs-app/src/middleware.ts
new file mode 100644
index 000000000..f73062bbd
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/src/middleware.ts
@@ -0,0 +1,20 @@
+import { AppMiddleware } from '@headstartwp/next/middlewares';
+import { NextRequest } from 'next/server';
+
+export const config = {
+	matcher: [
+		/*
+		 * Match all paths except for:
+		 * 1. /api routes
+		 * 2. /_next (Next.js internals)
+		 * 3. /fonts (inside /public)
+		 * 4. all root files inside /public (e.g. /favicon.ico)
+		 */
+		'/((?!api|cache-healthcheck|_next|fonts[\\w-]+\\.\\w+).*)',
+	],
+};
+
+export async function middleware(req: NextRequest) {
+	// @ts-expect-error
+	return AppMiddleware(req, { appRouter: true });
+}
diff --git a/projects/wp-polylang-nextjs-app/tsconfig.json b/projects/wp-polylang-nextjs-app/tsconfig.json
new file mode 100644
index 000000000..0471d5fc7
--- /dev/null
+++ b/projects/wp-polylang-nextjs-app/tsconfig.json
@@ -0,0 +1,35 @@
+{
+  "compilerOptions": {
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "strict": false,
+    "noEmit": true,
+    "incremental": true,
+    "module": "esnext",
+    "esModuleInterop": true,
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "jsx": "preserve",
+    "plugins": [
+      {
+        "name": "next"
+      }
+    ],
+    "forceConsistentCasingInFileNames": true
+  },
+  "include": [
+    "next-env.d.ts",
+    ".next/types/**/*.ts",
+    "**/*.ts",
+    "**/*.tsx"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
+}