Raqam
Guides

Next.js App Router

Using raqam with Next.js App Router — client components, Server Components, and edge functions.

Client components

raqam is a client-side library (it uses React hooks, DOM APIs, and browser Intl). All components and hooks must run in a Client Component.

// components/price-input.tsx
"use client";

import { NumberField } from "raqam";

export function PriceInput({ value, onChange }: {
  value: number | null;
  onChange: (v: number | null) => void;
}) {
  return (
    <NumberField.Root
      locale="en-US"
      formatOptions={{ style: "currency", currency: "USD" }}
      value={value}
      onChange={onChange}
    >
      <NumberField.Label>Price</NumberField.Label>
      <NumberField.Group>
        <NumberField.Decrement>−</NumberField.Decrement>
        <NumberField.Input />
        <NumberField.Increment>+</NumberField.Increment>
      </NumberField.Group>
    </NumberField.Root>
  );
}

The React entries (raqam and raqam/react) ship with "use client"; prepended to their bundles, so you can import them directly in Client Components without any extra wrapper. raqam/core and raqam/server do not carry the directive — they have zero React dependency and are safe to import from Server Components.

Server Components — formatting only

For SSR formatting (price display, report generation, email templates), import from raqam/server — it has zero React dependency and runs in any server context including Edge Runtime:

// app/products/page.tsx  (Server Component)
import { createFormatter } from "raqam/server";
import { presets } from "raqam/server";

const fmt = createFormatter({
  locale: "en-US",
  formatOptions: presets.currency("USD"),
});

export default function ProductsPage() {
  const price = fmt.format(1234.56);   // "$1,234.56"

  return <div>Price: {price}</div>;
}

raqam/server is an alias for raqam/core. It exports createFormatter, createParser, normalizeDigits, registerLocale, and presets.

Pattern: Server Component + Client Input

A common pattern: the Server Component renders the page shell and passes formatted prices as props, while a nested Client Component handles the editable field.

// app/checkout/page.tsx  (Server Component)
import { createFormatter } from "raqam/server";
import { CheckoutForm } from "./checkout-form";

export default async function CheckoutPage() {
  const product = await getProduct();
  const formatter = createFormatter({
    locale: "en-US",
    formatOptions: { style: "currency", currency: "USD" },
  });

  return (
    <CheckoutForm
      productName={product.name}
      defaultPrice={product.price}
      formattedPrice={formatter.format(product.price)}
    />
  );
}
// app/checkout/checkout-form.tsx  (Client Component)
"use client";

import { useState } from "react";
import { NumberField } from "raqam";

export function CheckoutForm({
  productName,
  defaultPrice,
  formattedPrice,
}: {
  productName: string;
  defaultPrice: number;
  formattedPrice: string;
}) {
  const [price, setPrice] = useState<number | null>(defaultPrice);

  return (
    <form>
      <p>Original price: {formattedPrice}</p>
      <NumberField.Root
        locale="en-US"
        name="price"
        formatOptions={{ style: "currency", currency: "USD" }}
        value={price}
        onChange={setPrice}
      >
        <NumberField.Label>Custom price</NumberField.Label>
        <NumberField.Input />
        <NumberField.HiddenInput />
      </NumberField.Root>
      <button type="submit">Checkout</button>
    </form>
  );
}

Locale plugins with App Router

Import locale plugins in your root layout (Server Component imports are fine for side-effect-only modules):

// app/layout.tsx
import "raqam/locales/fa";
import "raqam/locales/ar";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return <html><body>{children}</body></html>;
}

Locale plugins register digit blocks in a module-level array. In Next.js, this happens once per server instance and persists across requests.

Edge Runtime

raqam/server (raqam/core) is Edge Runtime compatible — it uses only Intl.NumberFormat which is available in all modern edge environments (Vercel Edge, Cloudflare Workers, Deno Deploy).

// edge-function.ts
import { createFormatter } from "raqam/server";

export const runtime = "edge";

export default function handler() {
  const fmt = createFormatter({ locale: "en-US" });
  return new Response(fmt.format(42));
}

On this page