enable-i18n

द्वारा vercel

दुकान टेम्पलेट में next-intl-आधारित i18n सक्षम करें — लोकेल-उपसर्गित URL, प्रति-लोकेल संदेश सूची, और एक लोकेल स्विचर। उपयोग करें जब उपयोगकर्ता "लोकेल…" चाहता है।

npx skills add https://github.com/vercel/shop --skill enable-i18n

Enable i18n (next-intl, no Markets)

Wire next-intl into the template so the storefront serves locale-prefixed URLs (/en-US/products/foo), loads per-locale message catalogs, and exposes a locale switcher. The template ships single-locale by default with clean URLs (/products/foo) — this skill restores the i18n machinery.

Use enable-shopify-markets instead if you want region-aware pricing/inventory/payments. That skill builds on the same routing layer plus Markets-specific operations. If you only want URL prefixing and translated copy, this skill is the right one.

Source of truth: lib/i18n/index.ts

The locale list lives in lib/i18n/index.ts as locales and enabledLocales. Always read those at the start of the skill — don't hardcode a list. Adding new locales means editing that file plus the message loaders/catalogs; everything downstream (routing, sitemap, alternates, switcher) reads from it. Do not add a locale-to-currency map; commerce currency comes from Shopify responses when Markets is enabled.

// lib/i18n/index.ts
export const locales = ["en-US", "en-GB", "de-DE", "fr-FR"] as const;
export const defaultLocale: Locale = "en-US";
export const enabledLocales: readonly Locale[] = locales;

What this skill turns on

  1. lib/i18n/routing.ts and lib/i18n/navigation.ts (next-intl)
  2. Route segment app/[locale]/ containing every page
  3. proxy.ts middleware running next-intl/middleware
  4. lib/params.ts getLocale() reading from next/root-params
  5. lib/i18n/request.ts loading messages by resolved locale
  6. Locale-prefixed canonicals + hreflang alternates in lib/seo.ts
  7. Sitemap entries per locale
  8. next.config.ts rewrites/redirects on /:locale/* sources
  9. app/(unlocalized)/page.tsx fallback redirect to default locale
  10. generateStaticParams on the root layout
  11. (If enable-shopify-menus already ran) Re-enable LocaleCurrencySelector in the megamenu

Cache Components compatibility — read this first

The template runs with cacheComponents: true (Next.js 16). That changes a few things this skill needs to handle correctly. Skipping any of these will produce build errors that look unrelated:

A. There must be no app/layout.tsx above app/[locale]/

For [locale] to be recognized as a root param, the dynamic segment must be the root layout. After Step 2, the file at app/layout.tsx should be gone (moved into app/[locale]/layout.tsx). If both exist, rootParams.locale() returns undefined.

B. setRequestLocale is not used

next-intl docs sometimes show setRequestLocale(locale) calls in layouts/pages. Don't add them under cacheComponents. That helper writes to a request-scoped store and forces dynamic rendering — it defeats the cache. The rootParams + request-config pattern below makes it unnecessary because the resolved locale is already a cache key.

C. Don't swap next/link to next-intl's <Link>

The straightforward instinct is to replace every import Link from "next/link" with import { Link } from "@/lib/i18n/navigation". Don't. next-intl's Link reads request context (locale) on render; in a server-component tree under cacheComponents, that triggers:

Error: Route "/[locale]/..." accessed [...] which is not defined in the `unstable_samples` of `instant`.

or a generic "blocking route" prerender failure.

Do this instead: keep next/link and let proxy.ts middleware redirect unprefixed paths (/products/foo/en-US/products/foo). Internal links work; there's a one-time middleware redirect on click for unprefixed hrefs. Trade a few redirects for a clean prerender.

If you must locale-prefix a programmatic URL (server actions, redirect(), permanentRedirect()), build the path yourself: `/${await getLocale()}/account/login`.

D. instant samples need locale in params

Any route that exports instant (currently: products [handle], collections [handle], search) needs locale added to every sample, or the build fails:

Error: Route "/[locale]/products/[handle]" accessed root param "locale"
       which is not defined in the `unstable_samples` of `instant`.

Fix:

export const instant = {
  unstable_samples: [
    {
      params: { locale: "en-US", handle: "__placeholder__" }, // ← add locale
      searchParams: { variant: "1" },
      cookies: [{ name: "shopify_cartId", value: null }],
    },
  ],
};

E. instant samples need headers declarations if any layout-level server component reads headers()

This is easy to forget. If you (or a downstream skill) adds a server component to the layout that calls headers() — e.g. a "Shipping to {postal}" bar reading x-vercel-ip-postal-code — every instant sample in the app must declare the headers it might access:

unstable_samples: [
  {
    params: { locale: "en-US", handle: "__placeholder__" },
    searchParams: { variant: "1" },
    cookies: [{ name: "shopify_cartId", value: null }],
    headers: [["x-vercel-ip-postal-code", null]], // ← add this
  },
],

null means "header may be absent." If you forget, the build error is explicit:

Error: Route "..." accessed header "x-vercel-ip-postal-code" which is not
       defined in the `unstable_samples` of `instant`. Add it to the
       sample's `headers` array, or `["...", null]` if it should be absent.

F. redirect() from next-intl doesn't return never

// BREAKS: TS doesn't narrow `session` after redirect
import { redirect } from "@/lib/i18n/navigation";
if (!session) redirect({ href: "/account/login", locale });
return session; // type error: session is CustomerSession | null

next-intl's redirect is typed to return void, so TypeScript doesn't treat it as control-flow-ending. Use next/navigation's redirect (which returns never) and prefix the locale yourself:

import { redirect } from "next/navigation";
import { getLocale } from "@/lib/params";

if (!session) redirect(`/${await getLocale()}/account/login`);
return session; // OK, narrowed

Step-by-step

Step 1: Routing config

Create lib/i18n/routing.ts:

import { defineRouting } from "next-intl/routing";
import { defaultLocale, enabledLocales } from ".";

export const routing = defineRouting({
  locales: enabledLocales, // pulled from lib/i18n/index.ts — never hardcode
  defaultLocale,
  localePrefix: "always",
});

Create lib/i18n/navigation.ts:

import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";

export const { Link, redirect, usePathname, useRouter } = createNavigation(routing);

Per "Cache Components compatibility C" above, Link here is mostly used by the locale switcher / programmatic routing in client components — not as a wholesale replacement for next/link.

Step 2: Move routes under app/[locale]/

Move every route file from app/ into app/[locale]/:

  • app/layout.tsxapp/[locale]/layout.tsx (becomes the root layout for the locale segment). Delete the original app/layout.tsx after the move — see compatibility A above; both files cannot coexist.
  • app/page.tsx, app/error.tsx, app/not-found.tsxapp/[locale]/...
  • app/account/, app/cart/, app/collections/, app/pages/, app/policies/, app/products/, app/search/app/[locale]/...

Stay at app/: api/, sitemap.xml/, sitemap/, robots.ts, global-error.tsx, globals.css, favicon.ico.

In the moved layout, fix import "./globals.css"import "../globals.css".

Update every PageProps<"/foo"> and LayoutProps<"/foo"> generic to include the locale segment: PageProps<"/[locale]/products/[handle]">, LayoutProps<"/[locale]">, etc.

Step 3: lib/params.ts reads from root params

import { notFound } from "next/navigation";
import { locale as rootLocale } from "next/root-params";
import { type Locale, locales } from "./i18n";

export async function getLocale(): Promise<Locale> {
  const current = await rootLocale();
  if (!current || !locales.includes(current as Locale)) notFound();
  return current as Locale;
}

Step 4: lib/i18n/request.ts loads messages by resolved locale

import { hasLocale } from "next-intl";
import { getRequestConfig } from "next-intl/server";
import { getLocale } from "../params";
import type enMessages from "./messages/en.json";
import { routing } from "./routing";

const messageLoaders: Record<string, () => Promise<{ default: typeof enMessages }>> = {
  "en-US": () => import("./messages/en.json"),
  // Add per-locale loaders as you ship message files. Missing locales fall
  // back to the default locale loader.
};

// We intentionally do NOT destructure `{ locale }` from the callback args.
// next-intl populates that arg from the `x-next-intl-locale` request header,
// and reading request headers from inside a cached tree forces the route
// dynamic — every `instant` sample then needs an explicit
// `headers: [["x-next-intl-locale", null]]` declaration. Going straight to
// `getLocale()` (which reads `next/root-params`) keeps the lookup cacheable.
export default getRequestConfig(async () => {
  const requested = await getLocale();
  const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
  const loader = messageLoaders[locale] ?? messageLoaders[routing.defaultLocale];
  const messages = (await loader()).default as typeof enMessages;
  return { locale, messages };
});

Step 5: proxy.ts middleware

import createMiddleware from "next-intl/middleware";
import { type NextRequest, NextResponse } from "next/server";
import { routing } from "@/lib/i18n/routing";

const handlei18n = createMiddleware(routing);

export default function middleware(request: NextRequest): NextResponse {
  const response = handlei18n(request);
  if (!response.ok) return response;

  const rewriteHeader = response.headers.get("x-middleware-rewrite");
  if (!rewriteHeader) return response;

  const rewriteTarget = new URL(rewriteHeader, request.url);
  const [, ...segments] = rewriteTarget.pathname.split("/");
  const normalized = new URL(`/${segments.filter(Boolean).join("/")}`, request.url);
  normalized.search = rewriteTarget.search;
  return NextResponse.rewrite(normalized, { headers: response.headers });
}

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

The file is proxy.ts (Next.js 16 convention), not middleware.ts.

Step 6: Internal hrefs — keep next/link

Per the cache-components note above, leave existing next/link imports alone. Middleware redirects unprefixed URLs to the active locale on click. The only places to use the next-intl-aware Link are inside client components that explicitly need to switch locales (e.g. a locale switcher) — and even then, usePathname() + useRouter().push() from next/navigation plus a manual segment swap is often cleaner under cacheComponents.

For programmatic redirects in server code, use next/navigation's redirect:

redirect(`/${await getLocale()}/account/login`);

Step 7: lib/seo.ts — locale-aware canonicals + hreflang alternates

import { defaultLocale, enabledLocales } from "./i18n";
import { getLocale } from "./params";

function withLocalePath(locale: string, pathname: string): string {
  const normalized = normalizePath(pathname);
  return normalized === "/" ? `/${locale}` : `/${locale}${normalized}`;
}

export async function buildAlternates({ pathname, searchParams }: {...}): Promise<Metadata["alternates"]> {
  const locale = await getLocale();
  const canonical = buildCanonicalPath(withLocalePath(locale, pathname), searchParams);

  const languages: Record<string, string> = {};
  for (const candidate of enabledLocales) {
    languages[candidate] = buildCanonicalPath(withLocalePath(candidate, pathname), searchParams);
  }
  languages["x-default"] = buildCanonicalPath(withLocalePath(defaultLocale, pathname), searchParams);

  return { canonical, languages };
}

buildAlternates is now async — update every caller to await.

Step 8: Sitemap per-locale entries

Edit app/sitemap/[shard]/route.ts. For every resource, emit one <url> per enabled locale and add <xhtml:link rel="alternate" hreflang="..." href="..." /> siblings inside each <url> pointing at the other locale variants. Add xmlns:xhtml="http://www.w3.org/1999/xhtml" to the <urlset> opening tag.

import { enabledLocales } from "@/lib/i18n";

function localizePath(locale: string, pathname: string): string {
  if (pathname === "/") return `/${locale}`;
  return `/${locale}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
}

// Inside renderShard(): for each item, for each locale, emit a <url> with
// a <loc> at the localized path and an <xhtml:link> per other locale.

app/sitemap.xml/route.ts (the index) doesn't need locale handling — it only lists shard URLs, which stay locale-agnostic.

Step 9: next.config.ts rewrites/redirects on /:locale/*

Existing markdown content-negotiation rewrites must move their source from /products/:handle to /:locale/products/:handle, etc. Destinations stay at /md/products/:handle, /md/collections/:handle, and /md/search — the handlers read locale from query params, not the URL path. Add the locale-prefixed redirect rules from the original config (/:locale/product*/:locale/products*).

Step 10: app/(unlocalized)/page.tsx fallback

import { permanentRedirect } from "next/navigation";
import { defaultLocale } from "@/lib/i18n";

export default function UnlocalizedRoot(): never {
  permanentRedirect(`/${defaultLocale}`);
}

This is a defensive fallback; with localePrefix: "always" middleware should already redirect /.

Step 11: generateStaticParams on the locale layout

import { locales } from "@/lib/i18n";

export const generateStaticParams = async () => {
  return locales.map((locale) => ({ locale }));
};

Step 12: Patch instant samples

Walk every route file that exports instant and add locale to each sample's params:

params: { locale: "en-US", handle: "__placeholder__" }

If any layout-level server component (e.g. a shipping/postal banner, geo-aware nav) reads headers(), also add a headers array to every sample:

headers: [["x-vercel-ip-postal-code", null]];

(See "Cache Components compatibility D/E" at the top.)

Step 13: (Conditional) Re-enable LocaleCurrencySelector in the megamenu

Only if the enable-shopify-menus skill has already been run and components/nav/megamenu/index.tsx exists. The selector component lives at components/nav/locale-currency.tsx (with a fallback at locale-currency-fallback.tsx). Wire it into both MegamenuDesktop and MegamenuMobile per the original instructions.

Verifying

After applying:

pnpm build         # should pass; routes prerender at /en-US, /en-GB, etc.
pnpm dev           # then:
curl -I /          # → 307 /en-US
curl -I /products  # → 307 /en-US/products
curl /sitemap.xml             # → sitemapindex listing shards
curl /sitemap/products-1.xml  # → entries with /en-US/... URLs + xhtml:link alternates
curl /en-US        # → 200 with <html lang="en-US">

Smoke-test checklist:

  • Build passes
  • Bare / redirects to default locale
  • Each enabled locale serves 200 at its prefix
  • <html lang> matches the URL's locale segment
  • Sitemap emits one entry per locale per page
  • Canonical + hreflang alternates appear in page metadata
  • Unprefixed internal links from existing next/link calls redirect (one extra hop, but correct)

vercel की और Skills

benchmark-sandbox
vercel
Vercel Sandboxes में vercel-plugin eval परिदृश्य चलाएँ, स्थानीय WezTerm पैनलों के बजाय। Claude Code + प्लगइन पूर्व-स्थापित के साथ अस्थायी microVMs प्रदान करता है,…
official
emil-design-eng
vercel
यह कौशल एमिल कोवाल्स्की के UI पॉलिश, कंपोनेंट डिज़ाइन, एनिमेशन निर्णयों और उन अदृश्य विवरणों पर दर्शन को एनकोड करता है जो सॉफ्टवेयर को शानदार महसूस कराते हैं।
official
vercel-react-best-practices
vercel
React और Next.js प्रदर्शन अनुकूलन दिशानिर्देश Vercel Engineering से। इस कौशल का उपयोग React/Next.js कोड लिखने, समीक्षा करने या रीफैक्टर करते समय किया जाना चाहिए…
official
vercel-react-best-practices
vercel
React और Next.js प्रदर्शन अनुकूलन दिशानिर्देश Vercel Engineering से। इस कौशल का उपयोग React/Next.js को लिखने, समीक्षा करने या रीफैक्टर करने के दौरान किया जाना चाहिए…
official
write-guide
vercel
एक तकनीकी गाइड तैयार करें जो प्रगतिशील उदाहरणों के माध्यम से एक वास्तविक दुनिया के उपयोग के मामले को सिखाता है। अवधारणाओं को केवल तब पेश किया जाता है जब पाठक को उनकी आवश्यकता होती है।
official
release
vercel
वर्सेल-प्लगइन जारी करें — गेट्स चलाएं, संस्करण बढ़ाएं, आर्टिफैक्ट्स उत्पन्न करें, कमिट करें और पुश करें। जब "रिलीज़ करें", "शिप करें", "बंप और पुश करें" या "कट अ रिलीज़" कहा जाए तो इसका उपयोग करें।
official
deepsec
vercel
dev3000 से Vercel प्रोजेक्ट चेकआउट पर DeepSec चलाएँ। एक-क्लिक DeepSec सेटअप, प्रोजेक्ट संदर्भ बूटस्ट्रैपिंग, सीमित प्रथम-पास प्रसंस्करण, और… के लिए उपयोग करें।
official
backport-pr
vercel
किसी मर्ज किए गए Next.js पुल रिक्वेस्ट को canary से पिछली रिलीज़ ब्रांच जैसे next-16-2 पर बैकपोर्ट करें। तब उपयोग करें जब उपयोगकर्ता बैकपोर्ट, चेरी-पिक, या खोलने के लिए कहे…
official