# Recipe: images with next/image and the Next.js App Router SDK

> Render Uniform asset parameters with next/image: read the asset with flattenValues, allow the Uniform image host, set the alt text and the size, and show a fallback.

Source: https://docs.uniform.app/docs/sdk/nextjs-app-router/recipes/images

**Goal:** show an image that an author selects in an asset parameter. Use the `next/image` component, so that Next.js optimizes the image.

## Prerequisites

- A Next.js app with the SDK. Refer to [Next.js App Router SDK](https://docs.uniform.app/docs/sdk/nextjs-app-router).
- A component definition with a parameter of the type **Asset**, for example `image`.
- The `@uniformdev/canvas` package, at the same version as the SDK:

  ```bash
  v=20.81.1-alpha.25.sha-f076f9b857
  npm install @uniformdev/canvas@$v
  ```

## The value of an asset parameter

The type of an asset parameter value is `AssetParamValue` from `@uniformdev/canvas`. It is an array of asset items, because an asset parameter can have more than one asset. Each item keeps its data in `fields`, and each field has a `type` and a `value`:

```json
[
  {
    "type": "image",
    "_id": "69197033-6dcb-49f2-91bf-a3c8246ff8ee",
    "_source": "uniform-assets",
    "fields": {
      "url": { "type": "text", "value": "https://img.uniform.global/p/…/some-image.png" },
      "title": { "type": "text", "value": "Asset Title" },
      "description": { "type": "text", "value": "This is an image showing something interesting" },
      "width": { "type": "number", "value": 1000 },
      "height": { "type": "number", "value": 563 }
    }
  }
]
```

`flattenValues` from `@uniformdev/canvas` changes each item into an object with plain values: `url`, `id`, `title`, `description`, `mediaType`, `width`, `height`, `size`, `focalPoint` and `custom`. Only `url` is always in the data. The other fields can be `undefined`.

| Call | Result |
| --- | --- |
| `flattenValues(image?.value)` | An array of flat assets |
| `flattenValues(image?.value, { toSingle: true })` | The first flat asset, or `undefined` for an empty array |

Give `flattenValues` the parameter value (`image?.value`), not the parameter object (`image`). The function reads the `fields` of each item.

## Step 1: Allow the Uniform image host

`next/image` loads remote images only from the hosts in `images.remotePatterns`. The Uniform asset library delivers images from `img.uniform.global` (US) and `img.eu.uniform.global` (EU):

`next.config.ts`

```ts
import { withUniformConfig } from "@uniformdev/next-app-router/config";
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      { protocol: "https", hostname: "img.uniform.global" },
      { protocol: "https", hostname: "img.eu.uniform.global" },
    ],
  },
};

export default withUniformConfig(nextConfig);
```

> **Note:**
>
> An asset can also come from a DAM integration or from a custom URL. Its `url` then has a different host. Add each of these hosts to `remotePatterns`. Without the host, `next/image` gives an error for the image.

## Step 2: Make the image component

`components/feature-image.tsx`

```tsx
import { type AssetParamValue, flattenValues } from "@uniformdev/canvas";
import type { ComponentParameter, ComponentProps } from "@uniformdev/next-app-router/component";
import Image from "next/image";

type FeatureImageParameters = {
  image?: ComponentParameter<AssetParamValue>;
};

export const FeatureImage = ({
  parameters: { image },
  context,
}: ComponentProps<FeatureImageParameters>) => {
  // The first asset of the parameter, with plain values: url, title, width, height and more.
  const asset = flattenValues(image?.value, { toSingle: true });

  // No image: show a placeholder in Canvas, and nothing on the live site.
  if (!asset?.url) {
    return context.isContextualEditing ? (
      <div className="flex aspect-video items-center justify-center bg-gray-100">Select an image</div>
    ) : null;
  }

  const alt = asset.description ?? asset.title ?? "";

  // The asset has its size: give it to next/image.
  if (asset.width && asset.height) {
    return (
      <Image
        src={asset.url}
        alt={alt}
        width={asset.width}
        height={asset.height}
        sizes="(max-width: 768px) 100vw, 50vw"
        className="h-auto w-full"
      />
    );
  }

  // The asset has no size: fill a box that has a fixed aspect ratio.
  return (
    <div className="relative aspect-video">
      <Image src={asset.url} alt={alt} fill sizes="(max-width: 768px) 100vw, 50vw" className="object-cover" />
    </div>
  );
};
```

Register the component in `components/resolveComponent.tsx`. Refer to [Next.js App Router SDK](https://docs.uniform.app/docs/sdk/nextjs-app-router).

## Step 3 (optional): Show more than one image

Without `toSingle`, `flattenValues` returns all assets of the parameter:

`components/gallery.tsx`

```tsx
import { type AssetParamValue, flattenValues } from "@uniformdev/canvas";
import type { ComponentParameter, ComponentProps } from "@uniformdev/next-app-router/component";
import Image from "next/image";

type GalleryParameters = {
  images?: ComponentParameter<AssetParamValue>;
};

export const Gallery = ({ parameters: { images } }: ComponentProps<GalleryParameters>) => {
  const assets = flattenValues(images?.value) ?? [];

  return (
    <div className="grid grid-cols-3 gap-4">
      {assets.map((asset, index) =>
        asset.url && asset.width && asset.height ? (
          <Image
            key={index}
            src={asset.url}
            alt={asset.description ?? asset.title ?? ""}
            width={asset.width}
            height={asset.height}
            sizes="33vw"
          />
        ) : null
      )}
    </div>
  );
};
```

## Notes

- **Alt text:** an asset has no `alt` field. The example uses the `description` of the asset, then its `title`. To set a different text in each component, add a text parameter for the alt text. For an image that is only decoration, use `alt=""`.
- **Width and height:** `next/image` must have `width` and `height`, or `fill`. For an image, Uniform gives the size of the original image in `width` and `height`. Other assets, and assets from a custom URL or a DAM integration, can have no size. Then the example uses `fill` in a box with a fixed aspect ratio.
- **Fallback:** an author can add a component and not select an image. Then `flattenValues` returns `undefined`. In Canvas, `context.isContextualEditing` is `true`, so the example shows a placeholder that tells the author to select an image. On the live site, it renders nothing.
- **The main image of a page:** for the largest image at the top of a page, add `preload` to `Image`. Next.js 16 replaced the `priority` property with `preload`.
- **Image transformations:** the Uniform image delivery API can crop and resize images with query parameters. `imageFrom` from `@uniformdev/assets` makes these URLs. Refer to [Image Delivery API](https://docs.uniform.app/docs/guides/composition/manage-assets/image-delivery-api).
