How to format currency amounts in Next.js (Pages Router) v16

Display prices with currency symbols and separators

Problem

Displaying monetary values requires coordinating two distinct localization concerns: the currency itself and the number format conventions of the user's region. A price of 1200.50 must appear as "$1,200.50" for US English users but as "1 200,50 €" for users in Germany. The currency symbol position, decimal separator, grouping separator, and spacing all vary by locale. When these elements are misaligned or hardcoded, users see unfamiliar formats that create doubt about whether the displayed amount is correct, undermining trust in pricing information.

Beyond visual consistency, incorrect currency formatting can cause real confusion. A user accustomed to commas as thousand separators may misread "1.200" as one point two, not twelve hundred. Similarly, a misplaced currency symbol can make prices look unprofessional or suggest the wrong currency entirely. Proper currency formatting respects both the currency code and the user's locale-specific number conventions, ensuring prices are immediately clear and trustworthy.

Solution

Format currency values by combining the ISO 4217 currency code with the user's active locale to produce region-appropriate number formatting. This approach delegates symbol placement, separator selection, and spacing rules to the internationalization library, which applies the correct conventions for each locale-currency pair. The result is a price display that matches user expectations without manual string manipulation or locale-specific logic in application code.

Steps

1. Create a reusable currency formatting component

Build a component that accepts a numeric value and currency code, then formats the price using the current locale from react-intl's context.

import { FormattedNumber } from "react-intl";

interface PriceProps {
  value: number;
  currency: string;
}

export default function Price({ value, currency }: PriceProps) {
  return <FormattedNumber value={value} style="currency" currency={currency} />;
}

The FormattedNumber component reads the locale from the nearest IntlProvider and applies both the currency symbol and locale-specific number formatting rules automatically.

2. Use the component in a page with dynamic pricing data

Render prices by passing numeric amounts and currency codes to the component, allowing the formatting to adapt to the user's locale.

import { GetServerSideProps } from "next";
import Price from "../components/Price";

interface Product {
  id: string;
  name: string;
  price: number;
  currency: string;
}

interface ProductPageProps {
  product: Product;
}

export default function ProductPage({ product }: ProductPageProps) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>
        <Price value={product.price} currency={product.currency} />
      </p>
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async () => {
  const product = {
    id: "1",
    name: "Wireless Headphones",
    price: 1299.99,
    currency: "USD",
  };

  return {
    props: {
      product,
    },
  };
};

The page fetches product data including the currency code and passes it to the Price component, which formats the amount according to the active locale.

3. Create a helper for imperative currency formatting

For scenarios where a React component cannot be used, such as setting attributes or preparing data for non-JSX contexts, expose a formatting function using the useIntl hook.

import { useIntl } from "react-intl";

export function useCurrencyFormatter() {
  const intl = useIntl();

  return (value: number, currency: string): string => {
    return intl.formatNumber(value, {
      style: "currency",
      currency,
    });
  };
}

This hook returns a function that formats currency values as strings, useful for aria labels, meta tags, or other text-only contexts where components are not appropriate.

4. Apply the formatter in attribute contexts

Use the imperative formatter to populate HTML attributes that require plain text rather than React elements.

import { useCurrencyFormatter } from "../hooks/useCurrencyFormatter";

interface ProductCardProps {
  name: string;
  price: number;
  currency: string;
  imageUrl: string;
}

export default function ProductCard({
  name,
  price,
  currency,
  imageUrl,
}: ProductCardProps) {
  const formatCurrency = useCurrencyFormatter();
  const priceLabel = formatCurrency(price, currency);

  return (
    <article aria-label={`${name}, ${priceLabel}`}>
      <img src={imageUrl} alt={name} />
      <h2>{name}</h2>
      <p>{priceLabel}</p>
    </article>
  );
}

The formatter produces a localized currency string that can be embedded in the aria-label attribute, ensuring assistive technologies announce prices in the correct format for the user's locale.