Next.js App Router
Client components
Section titled “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.
"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> );}Server Components — formatting only
Section titled “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
Section titled “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
Section titled “Locale plugins with App Router”Import locale plugins in your root layout (Server Component imports are fine for side-effect-only modules):
import "raqam/locales/fa";import "raqam/locales/ar";
export default function RootLayout({ children }: { children: React.ReactNode }) { return <html><body>{children}</body></html>;}Edge Runtime
Section titled “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).
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));}