Skip to content

RESOURCES / BLOG

Build a React Travel Gallery With create-cloudinary-react and the Cloudinary Upload Widget

Building upload features in React sounds simple at first: add file input, add previews. Then users want drag-and-drop, progress bars, retries, and support for multiple files at once. Your beginner project has now snowballed into an even bigger one.

In this guide, you’ll build a simple travel gallery using React and the Cloudinary Upload Widget. Instead of building the upload UI from scratch, you’ll open a ready-made upload modal from a button.

By the end, users will be able to upload travel photos, get instant feedback, and see the images appear in the gallery. Cloudinary will handle the upload flow, progress, file sources, and image delivery for you, while React will handle the button, gallery state, and user feedback.

Image upload starts with a basic file input:

<input type="file" />
Code language: HTML, XML (xml)

But users need more than a simple button: They want to upload many photos simultaneously, drag files into the browser, and get real-time progress updates while each file uploads (just to make sure the app isn’t frozen).

Building this yourself means you also have to handle:

  • Error states. What happens if the Wi-Fi drops mid-upload?
  • Retries. How do you resume a failed 10MB photo upload?
  • Previews. Showing a thumbnail before the user hits “submit.”
  • File sources. Supporting uploads from Google Drive, Dropbox, or a device camera.

For a beginner React project, this complexity can slow you down before you’ve even made any meaningful progress. Instead of spending days on tedious upload logic, you should focus on the core of your app. In this case, that app is a travel gallery where users can add and view their favorite memories.

The Cloudinary Upload Widget gives you a ready-made upload modal, so you don’t have to build the full upload flow yourself. It includes:

  • Local file and camera uploads.
  • URL, Google Drive, and Dropbox imports.
  • Multi-file support with upload progress.
  • Optional cropping and editing.
  • Success and error callbacks for your app logic.

In this project, React triggers the widget from a single button. Once a user uploads their photos, Cloudinary returns the details for each image. You’ll then use that data to update the travel gallery instantly.

This architecture keeps your app lean: React handles the gallery UI, while Cloudinary manages the complex upload work.

Your small travel gallery app in React will enable users to:

  • Click a button to open the Cloudinary Upload Widget.
  • Upload one or more travel photos.
  • See a toast when an upload works or fails.
  • Add uploaded images to the gallery without refreshing the page.
  • Display clean, optimized images from Cloudinary.

To achieve this, use the create-cloudinary-react starter kit, which provides a React environment where Cloudinary is already wired in, allowing you to skip the tedious configuration and jump straight into building your feature.

Start by creating a new React project with create-cloudinary-react.

npx create-cloudinary-react react-travel-gallery 

This command creates a Vite, React, and TypeScript project with Cloudinary setup included.

During setup, you’ll be asked for:

Prompt What to Add
Project name react-travel-gallery
Cloud name Your Cloudinary cloud name
Upload preset Your unsigned upload preset

You can find your cloud name in your Cloudinary dashboard.

For the upload preset, go to your Cloudinary settings and create an unsigned upload preset. This lets the browser upload images directly to Cloudinary without using a backend.

After setup, move into the project and start it:

cd react-travel-gallery
npm run dev

You should now have a working React app ready for the travel gallery.

The Cloudinary Upload Widget needs a script before React can open it.

In this starter, the script is added to index.html:

<script
  src="https://upload-widget.cloudinary.com/latest/global/all.js"
  async
></script>
Code language: HTML, XML (xml)

This loads the widget in the browser.

Since the script uses async, it may load after React starts. That’s why the starter checks if the widget is ready before opening it.

Once it’s ready, React can call the widget from a button click.

You don’t need to build the upload modal, progress bar, or file picker yourself. Cloudinary provides that UI for you.

Next, create a reusable upload button.

This component will open the Cloudinary Upload Widget when the user clicks it.

The key idea is simple:

<UploadWidget
  onUploadSuccess={handleUploadSuccess}
  onUploadError={handleUploadError}
  buttonText="Upload Travel Photos"
/>
Code language: HTML, XML (xml)

Inside the component, Cloudinary creates the widget with your cloud name and upload preset.

You can also control where users upload from:

sources: ['local', 'camera', 'url', 'google_drive', 'dropbox'],
multiple: true,
Code language: JavaScript (javascript)

So users can upload from their device, camera, a URL, Google Drive, or Dropbox. The multiple: true option allows users to upload more than one travel photo at a time. This gives you a better upload flow without building drag-and-drop, progress bars, or file source options yourself.

Now you’ll need to respond after each upload.

The Upload Widget gives us callbacks for upload results. You can use them to update the gallery and show feedback.

function handleUploadSuccess(result: CloudinaryUploadResult) {
  dispatch({ type: "add", image: result });
  toast.success("Travel photo added!");
}

function handleUploadError(error: Error) {
  toast.error(`Upload failed: ${error.message}`);
}
Code language: JavaScript (javascript)

When the upload works, you’ll add the new image to the gallery state. When it fails, show a clear error message.

This is a small detail, but it improves the user experience. Users should know right away if their upload worked or failed.

After an upload works, Cloudinary returns details about the image.

The most important value is the public_id. Think of the public_id as the image name Cloudinary uses to find and deliver the asset. Instead of saving a full image URL, you can store the public_id and build the image URL when you render the gallery.

function buildThumbnail(publicId: string) {
  return cld
    .image(publicId)
    .resize(thumbnail().width(400).height(400))
    .delivery(format(auto()))
    .delivery(quality(autoQuality()));
}
Code language: JavaScript (javascript)

Then render the image with Cloudinary’s React SDK:

<AdvancedImage cldImg={buildThumbnail(image.public_id)} />
Code language: HTML, XML (xml)

Now each uploaded travel photo will appear in the gallery without a page refresh. React will update the page, Cloudinary will deliver the image.

A travel gallery should load fast. Large images can slow down the page, especially when users upload photos from phones or cameras. Cloudinary helps by transforming images through the delivery URL.

For example:

function buildThumbnail(publicId: string) {
  return cld
    .image(publicId)
    .resize(thumbnail().width(400).height(400))
    .delivery(format(auto()))
    .delivery(quality(autoQuality()));
}
Code language: JavaScript (javascript)

This does three useful things:

  • Creates a clean square thumbnail.
  • Sends the best image format for the browser.
  • Balances image quality and file size.

You can also add lazy loading and a blur placeholder:

<AdvancedImage
  cldImg={buildThumbnail(image.public_id)}
  plugins={[
    placeholder({ mode: "blur" }),
    lazyload(),
  ]}
/>
Code language: HTML, XML (xml)

This makes the gallery feel smoother while images load.

Good upload flows give users clear feedback. When someone uploads a travel photo, they should know what happened right away.

Use toast messages for this:

toast.success("Travel photo added!");
toast.error("Upload failed. Please try again.");
Code language: JavaScript (javascript)

You can also improve the gallery by adding:

  • A loading state while images load.
  • A clear upload button label.
  • A short empty state when no photos exist.
  • A preview before opening the full image.
  • A success message after each upload.

These small details make the app feel more complete.

When you build a custom uploader, you’re responsible for every moving part. By using the widget, you offload the complexity so you can focus on your React application.

The Feature Building It Custom With Cloudinary Widget
Upload UI Design and build file picker Ready-made, polished modal
Interactions Write drag-and-drop logic Included natively
User Progress Track and sync progress bars Included natively
File Sources Restricted to local files Camera, URL, and social imports
Infrastructure Build storage and delivery Handled by Cloudinary’s CDN

The result is a travel gallery UI. Cloudinary handles the entire upload flow, image storage, and high-performance delivery.

At this point, the travel gallery has a complete upload flow. Users can click one button, open the Cloudinary Upload Widget, and upload travel photos from different sources.

After upload, the app can:

  • Show a success or error toast.
  • Add the new image to the gallery.
  • Display optimized thumbnails.
  • Load images with better performance.
  • Avoid page refreshes.

You can explore the full project here:

No adding extra upload logic from scratch. React handles the app state and gallery UI. Cloudinary handles the upload flow, image storage, and image delivery.

Ready to start your own projects with Cloudinary? Sign up for a free account today.

Why should I use the Cloudinary Upload Widget instead of a custom file input?

The widget manages complex UI tasks like drag and drop, progress bars, and retries natively. This allows you to focus on building your React gallery features instead of debugging tedious file upload logic.

What is create-cloudinary-react and how does it help?

It is a starter kit that provides a pre-configured Vite and React environment. This tool helps you skip the initial configuration and jump straight into building media-rich applications with Cloudinary features.

How does React handle upload results from the widget?

You use callback functions like onUploadSuccess to capture the result. These functions receive the image data from Cloudinary, allowing you to update your React state instantly and display success notifications to the user.

Can I upload images from sources other than my local device?

Yes. The widget supports many external sources including your camera, Google Drive, Dropbox, and direct URLs. You can enable these additional sources by adding them to the widget configuration array.

How do I optimize gallery performance in a React application?

You should use the Cloudinary React SDK to apply automatic format and quality transformations. Adding plugins for lazy loading and blur placeholders ensures your gallery remains fast even as you add more high-resolution images.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free