# react-photo-album computes masonry and rows layouts before rendering

> React Photo Album is a responsive gallery component for React that offers rows, columns and masonry layouts, computes row breaks with dynamic programming, and can emit srcset and sizes attributes for responsive images. It suits teams that need a gallery that renders correctly on the server, and it asks you for photo dimensions up front.

**igordanchenko/react-photo-album** — Responsive photo gallery component for React

- Repository: https://github.com/igordanchenko/react-photo-album
- Website: https://react-photo-album.com
- Stars: 788 · Forks: 39
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/igordanchenko-react-photo-album

## Three layouts, and the rows layout is the interesting one

React Photo Album is a responsive photo gallery component with rows, columns and masonry layouts, inspired by react-photo-gallery and re-engineered from the ground up. The rows layout is the one worth understanding because it is not a naive implementation. It fills the container by arranging photos into rows of similar height, with each row height as close to the targetRowHeight as possible. The approach is inspired by the Knuth and Plass line breaking algorithm and uses dynamic programming to find optimal row breaks in a directed acyclic graph where photo indices are the nodes and each possible row is an edge connecting its first and last photo. The cost of each edge is a weighted squared deviation from the target row height. The result is rows of consistent height and photos that are neither stretched nor abnormally shrunk. The documentation credits this with fixing the familiar problems of panoramas shrinking a row, stragglers, and stretched images in the last row, all of which are what a naive greedy implementation produces.

## Columns balances groups, masonry just picks the shortest column

The other two layouts solve different problems and it helps to know which one you actually want. Columns layout fills the container by arranging photos into a predefined number of columns, set through the columns parameter, and uses a dynamic programming algorithm to find the optimal partition of photos into that number of groups so the layout is as balanced as possible. Column widths are then adjusted proportionally based on the aspect ratios of their content so that all columns render at equal height. Masonry layout is much simpler: it arranges photos into columns of equal width by placing each photo into the shortest column. The trade-off the documentation states plainly is that masonry does not fill the container space flush to its bottom edge, even though the columns end up close to equal height. So if you need the album to occupy the full height of its box, masonry will leave a gap and rows or columns will not. That is a rendering difference you will see immediately, not a subtle one.

## Responsive images come from srcset entries, not from a CDN

The component can automatically produce sizes and srcset image attributes, and in the case of SSR it includes those attributes in the server-rendered markup so browsers pick the right resolution for the viewport. The mechanism is that you provide the different resolutions yourself in the photo srcSet attribute, and the component works out the candidate list. You can also pass a sizes prop describing the width of the album in various viewports, which the documentation recommends to improve responsiveness and bandwidth use. The data shape is straightforward. A photo carries src, width and height, and optionally a srcSet array of entries with their own src, width and height:

```tsx
import { RowsPhotoAlbum } from "react-photo-album";

import "react-photo-album/rows.css";

const photos = [
  {
    src: "/image1_800x600.jpg",
    width: 800,
    height: 600,
    srcSet: [
      { src: "/image1_400x300.jpg", width: 400, height: 300 },
      { src: "/image1_200x150.jpg", width: 200, height: 150 },
    ],
  },
  {
    src: "/image2_1600x900.jpg",
    width: 1600,
    height: 900,
    srcSet: [
      { src: "/image2_800x450.jpg", width: 800, height: 450 },
      { src: "/image2_400x225.jpg", width: 400, height: 225 },
    ],
  },
];
```

The widths and heights are inputs, not outputs. Nothing in the component fetches an image to measure it, which is what makes the layout deterministic and cheap to render, and also what means you have to know the dimensions.

## The first render needs a container width or it renders nothing

Server-side rendering is where most gallery integrations go wrong, and this component addresses it directly. React Photo Album uses CSS flexbox and CSS calc functions to calculate image dimensions on the client, and thanks to that approach the server-rendered markup looks pixel-perfect on the client even before hydration. The catch is that you have to tell it how wide the container is. To enable server-side rendering you specify the defaultContainerWidth prop, otherwise the component produces empty markup on the server and renders on the client only after hydration. The documentation is also honest about the failure mode. Unless your album is of a constant width that always matches the defaultContainerWidth value, you will most likely see a layout shift immediately after hydration. The alternative is a fallback skeleton in the skeleton prop, which is rendered during SSR and swapped for the real markup after hydration. The examples directory includes a zero-cls-ssr folder, which is where to look for the pattern that avoids the shift.

## Installing and rendering a first gallery

The package installs from npm:

```shell
npm install react-photo-album
```

The requirements are React 18+, Node 18+ and a modern ESM-compatible bundler. The minimal setup example from the documentation is short, and note that the stylesheet for the chosen layout is imported separately:

```tsx
import { RowsPhotoAlbum } from "react-photo-album";

import "react-photo-album/rows.css";

const photos = [
  { src: "/image1.jpg", width: 800, height: 600 },
  { src: "/image2.jpg", width: 1600, height: 900 },
];

export default function Gallery() {
  return <RowsPhotoAlbum photos={photos} />;
}
```

There are three named components to import, RowsPhotoAlbum, ColumnsPhotoAlbum and MasonryPhotoAlbum, and the package.json exports a matching CSS file for each, rows.css, columns.css and masonry.css, plus a shared styles.css. The sideEffects field marks the CSS as side-effectful so a bundler will not tree-shake those imports away, which is a small but real detail when you are wiring up a build.

## Package exports point at separate entry points for SSR, scroll and server

The package.json is published as an ES module with a single main entry and three additional subpath exports. The root export carries the type definitions and the default module. Then there is an ssr entry with its own types and module, a scroll entry, and a server entry, each of them a separate dist path with matching type declarations under typesVersions. The files array publishes only dist, and the version field is set to 0.0.0-semantic-release, which is the placeholder that semantic-release replaces at publish time. Engines require Node 18 or newer, and the build itself runs a chain of tsc, vite build, rollup for the declaration files and lightningcss for the stylesheets, orchestrated by npm-run-all. The practical consequence for a consumer is that the SSR helpers are not bundled into the main entry, so a server rendered application pays for them only when it imports the ssr path, and a browser bundle that never scrolls a gallery does not carry the scroll module.

## Where this gallery component stops

The limitation that decides this library is the one already mentioned, so it is worth stating plainly. Photo dimensions are inputs. The component does not fetch images to discover their aspect ratios, and it does not measure the DOM on the client to work out the layout, which is exactly what makes it fast and SSR friendly. If your images live behind a URL and their dimensions are not known when the page is rendered, you have to supply that information yourself, and the layout will be wrong until you do. The second limitation is scope. This is a layout component, not a gallery product. The examples directory lists lightbox, infinite-scroll, sortable-gallery and playground folders, which are worked examples rather than shipped features, so a lightbox is something you add. The third is version churn. Releases v3.6.2, v3.6.1 and v3.6.0 landed on 2026-10-17, 2026-07-30 and 2026-04-13, with the last push on 2026-10-21, so the project moves quickly and the changelog in the repository and the release page are the places to check when a minor bump changes layout behaviour.

## Conclusion

Adopt react-photo-album when you need a gallery that lays out quickly on the server without a client side measuring pass, when you already know the pixel dimensions of your images, and when you want srcset generation rather than hand written image markup. Do not adopt it when your image dimensions are unknown at build time, since the component cannot measure a remote image before layout, and do not expect it to be a lightbox, a lightbox library or a full gallery platform. Verify first that your images carry width and height metadata, that your container width is constant across viewports or that you set defaultContainerWidth, and that you import the stylesheet for the layout you choose.

## FAQ

### How do I install react-photo-album and render a gallery?

Run npm install react-photo-album, then import a layout component such as RowsPhotoAlbum along with its stylesheet, and pass a photos array where each entry has src, width and height. The requirements are React 18+, Node 18+ and a modern ESM-compatible bundler.

### Why does my react-photo-album gallery shift layout after hydration?

The documentation states that unless the album has a constant width matching the defaultContainerWidth value, you will most likely see a layout shift after hydration. The fix is to set defaultContainerWidth for server-side rendering, or to provide a fallback skeleton in the skeleton prop.

### What is the difference between the rows, columns and masonry layouts?

Rows arranges photos into rows of similar height using dynamic programming over possible row breaks. Columns partitions photos into a fixed number of columns and adjusts their widths so they render at equal height. Masonry places each photo into the shortest column, which leaves the container not filled flush to its bottom edge.

### Does react-photo-album support responsive images?

Yes. It can produce sizes and srcset attributes automatically, including in server-rendered markup, if you provide the different resolutions in the photo srcSet attribute. You can also pass a sizes prop describing the album width across viewports.

## Sources

- [igordanchenko/react-photo-album on GitHub](https://github.com/igordanchenko/react-photo-album)
- [License: MIT](https://github.com/igordanchenko/react-photo-album/blob/main/LICENSE)
- [Project website](https://react-photo-album.com)
- [README](https://github.com/igordanchenko/react-photo-album/blob/main/README.md)
- [Releases](https://github.com/igordanchenko/react-photo-album/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/igordanchenko-react-photo-album
