Skip to content

Commit

Permalink
feat: add SSR fallback skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
igordanchenko committed Jun 26, 2024
1 parent 2bce4de commit 0ea7a26
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 15 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ enable server-side rendering, specify the `defaultContainerWidth` prop.
Otherwise, React Photo Album produces an empty markup on the server and renders
on the client only after hydration. Please note that unless your photo album is
of constant width that always matches the `defaultContainerWidth` value, you
will most likely see a layout shift immediately after hydration.
will most likely see a layout shift immediately after hydration. Alternatively,
you can provide a fallback skeleton in the `skeleton` prop that will be rendered
in SSR and swapped with the actual photo album markup after hydration.

## Credits

Expand Down
57 changes: 47 additions & 10 deletions docs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,6 @@ The following props are applicable to all three layouts.
<td>function</td>
<td>Photo click callback. See [Click Handler](#ClickHandler) for details.</td>
</tr>
<tr>
<td>defaultContainerWidth</td>
<td>number</td>
<td>
The default container width in server-side rendering (SSR). This prop is
required to enable server-side rendering. If absent,
[React Photo Album](/) produces an empty markup on the server and
renders on the client only after hydration.
</td>
</tr>
<tr>
<td>sizes</td>
<td>object</td>
Expand Down Expand Up @@ -127,6 +117,22 @@ The following props are applicable to all three layouts.
Custom render functions. See [Render Functions](#RenderFunctions) for details.
</td>
</tr>
<tr>
<td>defaultContainerWidth</td>
<td>number</td>
<td>
The default container width in server-side rendering (SSR).
See [Default Container Width](#Server-SideRendering(SSR)_DefaultContainerWidth) for details.
</td>
</tr>
<tr>
<td>skeleton</td>
<td>ReactNode</td>
<td>
Fallback skeleton in SSR.
See [Skeleton](#Server-SideRendering(SSR)_Skeleton) for details.
</td>
</tr>
</tbody>
</table>

Expand Down Expand Up @@ -679,6 +685,37 @@ the content container padding and the left-hand side navigation menu:
/>
```

## Server-Side Rendering (SSR)

By default, [React Photo Album](/) produces an empty markup on the server, but
several alternative solutions are available.

### Default Container Width

To render photo album markup on the server, you can specify the
`defaultContainerWidth` value. It is a perfect SSR solution if your photo album
has a constant width in all viewports (e.g., an image picker in a fixed-size
sidebar). However, if the client-side photo album width doesn't match the
`defaultContainerWidth`, you are almost guaranteed to see a layout shift.

```tsx
<RowsPhotoAlbum photos={photos} defaultContainerWidth={800} />
```

### Skeleton

Alternatively, you can provide a fallback skeleton in the `skeleton` prop that
will be rendered in SSR and swapped with the actual photo album markup after
hydration. This approach allows you to reserve a blank space for the photo album
markup and avoid a flash of below-the-fold content during hydration.

```tsx
<RowsPhotoAlbum
photos={photos}
skeleton={<div style={{ width: "100%", minHeight: 800 }} />}
/>
```

## Previous Versions

Are you looking for documentation for one of the previous versions?
Expand Down
7 changes: 6 additions & 1 deletion src/client/columns/ColumnsPhotoAlbum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function ColumnsPhotoAlbum<TPhoto extends Photo>({
sizes,
breakpoints,
defaultContainerWidth,
skeleton,
...restProps
}: ColumnsPhotoAlbumProps<TPhoto>) {
const { containerRef, containerWidth } = useContainerWidth(breakpoints, defaultContainerWidth);
Expand All @@ -37,6 +38,10 @@ export default function ColumnsPhotoAlbum<TPhoto extends Photo>({
);

return (
<StaticPhotoAlbum layout="columns" ref={containerRef} {...{ model, componentsProps, render, onClick, sizes }} />
<StaticPhotoAlbum
layout="columns"
ref={containerRef}
{...{ model, componentsProps, render, onClick, sizes, skeleton }}
/>
);
}
7 changes: 6 additions & 1 deletion src/client/masonry/MasonryPhotoAlbum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function MasonryPhotoAlbum<TPhoto extends Photo>({
sizes,
breakpoints,
defaultContainerWidth,
skeleton,
...restProps
}: MasonryPhotoAlbumProps<TPhoto>) {
const { containerRef, containerWidth } = useContainerWidth(breakpoints, defaultContainerWidth);
Expand All @@ -37,6 +38,10 @@ export default function MasonryPhotoAlbum<TPhoto extends Photo>({
);

return (
<StaticPhotoAlbum layout="masonry" ref={containerRef} {...{ model, componentsProps, render, onClick, sizes }} />
<StaticPhotoAlbum
layout="masonry"
ref={containerRef}
{...{ model, componentsProps, render, onClick, sizes, skeleton }}
/>
);
}
9 changes: 8 additions & 1 deletion src/client/rows/RowsPhotoAlbum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default function RowsPhotoAlbum<TPhoto extends Photo>({
sizes,
breakpoints,
defaultContainerWidth,
skeleton,
...rest
}: RowsPhotoAlbumProps<TPhoto>) {
const { containerRef, containerWidth } = useContainerWidth(breakpoints, defaultContainerWidth);
Expand Down Expand Up @@ -64,5 +65,11 @@ export default function RowsPhotoAlbum<TPhoto extends Photo>({
}
}

return <StaticPhotoAlbum layout="rows" ref={containerRef} {...{ model, componentsProps, render, onClick, sizes }} />;
return (
<StaticPhotoAlbum
layout="rows"
ref={containerRef}
{...{ model, componentsProps, render, onClick, sizes, skeleton }}
/>
);
}
8 changes: 7 additions & 1 deletion src/core/static/StaticPhotoAlbum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import PhotoComponent from "./PhotoComponent";
import { srcSetAndSizes, unwrap } from "../utils";
import { CommonPhotoAlbumProps, ComponentsProps, LayoutModel, Photo, Render } from "../../types";

type StaticPhotoAlbumProps<TPhoto extends Photo> = Pick<CommonPhotoAlbumProps<TPhoto>, "sizes" | "onClick"> & {
type StaticPhotoAlbumProps<TPhoto extends Photo> = Pick<
CommonPhotoAlbumProps<TPhoto>,
"sizes" | "onClick" | "skeleton"
> & {
layout?: string;
model?: LayoutModel<TPhoto>;
render?: Render<TPhoto>;
Expand All @@ -18,6 +21,7 @@ export default forwardRef(function StaticPhotoAlbum<TPhoto extends Photo>(
layout,
sizes,
model,
skeleton,
onClick: onClickCallback,
render: { container, track, photo: renderPhoto, ...restRender } = {},
componentsProps: {
Expand Down Expand Up @@ -100,6 +104,8 @@ export default forwardRef(function StaticPhotoAlbum<TPhoto extends Photo>(
</Component>
);
})}

{containerWidth === undefined && skeleton}
</Component>
);
}) as <TPhoto extends Photo>(
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface CommonPhotoAlbumProps<TPhoto extends Photo = Photo> {
render?: ResponsiveParameter<Render<TPhoto>>;
/** Additional HTML attributes to be passed to the rendered elements. */
componentsProps?: ComponentsProps<TPhoto> | ((containerWidth?: number) => ComponentsProps<TPhoto>);
/** Fallback skeleton in SSR. */
skeleton?: React.ReactNode;
}

/** Rows photo album props */
Expand Down

0 comments on commit 0ea7a26

Please sign in to comment.