Atlas CMS
Use Cases

Portfolio

Manage and display rich media galleries for portfolios or case studies using Atlas CMS.

Portfolio & Galleries

For creative agencies, photographers, and developers, a portfolio site relies heavily on high-quality media. Atlas CMS handles rich media assets elegantly, returning full URLs, dimensions, and alt text for images to optimize rendering in frontend frameworks like Next.js.

In this guide, we'll build a Portfolio Case Study page that features an image gallery.

What We're Building

We'll assume a case-study content type with a field named gallery (an array of images).


1. API Helper

lib/atlas.ts
const BASE = process.env.ATLAS_BASE_URL + '/api/v1/public'
const headers = { 'X-API-Key': process.env.ATLAS_API_KEY! }

export interface MediaAsset {
  url: string
  width: number
  height: number
  alt?: string
}

export interface CaseStudy {
  slug: string
  data: {
    title: string
    client: string
    cover: MediaAsset
    gallery: MediaAsset[] // Array of images
  }
}

export async function getCaseStudy(slug: string): Promise<CaseStudy | null> {
  const res = await fetch(`${BASE}/entries/${slug}?type=case-study`, {
    headers,
    next: { revalidate: 60 },
  })
  if (!res.ok) return null
  const { data } = await res.json()
  return data
}

When using next/image, you must provide the width and height properties to prevent layout shift. Atlas CMS conveniently provides these exact dimensions for every uploaded image.

app/work/[slug]/page.tsx
import Image from 'next/image'
import { notFound } from 'next/navigation'
import { getCaseStudy } from '@/lib/atlas'

export default async function CaseStudyPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const project = await getCaseStudy(slug)

  if (!project) notFound()

  const { title, client, cover, gallery } = project.data

  return (
    <main className="container py-12">
      <div className="mb-12">
        <h1 className="text-5xl font-bold mb-2">{title}</h1>
        <p className="text-xl text-muted">Client: {client}</p>
      </div>

      {cover && (
        <Image
          src={cover.url}
          alt={cover.alt ?? title}
          width={cover.width}
          height={cover.height}
          priority
          className="w-full rounded-2xl mb-16 shadow-lg"
        />
      )}

      <h2 className="text-3xl font-bold mb-8">Project Gallery</h2>

      {/* Masonry or Grid Layout for Gallery */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
        {gallery?.map((image, index) => (
          <div key={index} className="relative overflow-hidden rounded-xl">
            <Image
              src={image.url}
              alt={image.alt ?? `Gallery image ${index + 1}`}
              width={image.width}
              height={image.height}
              className="object-cover w-full h-full hover:scale-105 transition-transform duration-300"
            />
          </div>
        ))}
      </div>
    </main>
  )
}

Next.js Image Optimization

Make sure you add the Atlas CMS CDN domain (e.g. cdn.atlas.latellu.com) to your next.config.mjs so that next/image can optimize the images.

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.atlas.latellu.com',
      },
    ],
  },
};

export default nextConfig;

On this page