> ## Documentation Index
> Fetch the complete documentation index at: https://cloudinary.com/documentation/llms.txt
> Use this file to discover all available pages before exploring further.

# Profile picture sample project


This guide demonstrates how to build a secure social media-style application that handles user-generated content (UGC) using Cloudinary's advanced capabilities. The app features a Profile page where users can manage their personal information and upload a profile picture, along with a Posts page where they can share thoughts and images.

Profile page

Posts page 

> **TIP**: :title=View the code

You can find the code for this sample project in [GitHub](https://github.com/cloudinary-devs/ugc-profile-posts).

## Overview

The app serves as a demonstration platform for handling user-generated content in a social media context. It implements these main features:

On the Profile page:

* The uploaded image is moderated for appropriate content and checked for malware before being displayed on the page.
* If the image is poor quality, then the quality is improved.
* The image is displayed as a square, focusing on the face, if there is one, or the most interesting part of the image, if not. 

On the Posts page:

* The post is displayed against the profile picture, which is resized and made circular with an outline.
* The uploaded image, if there is one, is moderated for appropriate content and checked for malware before being displayed on the page. 
* The post image is displayed with padding, if required, to show the whole image in a dedicated space.

### Key Cloudinary features

Learn about how each of these Cloudinary features have been implemented in this app:

* [Upload preset](#upload_preset): the instructions for uploading images
* [Upload widget](#upload_widget): the method of uploading images
* [Moderation and malware detection](#moderation_and_malware_detection): ensuring uploaded images are appropriate and safe
* [Quality analysis](#quality_analysis): analyzing and improving image quality 
* [Transformations and optimizations](#transformations_and_optimizations): modifying images on the fly

## Try it out

Here's the app in action:

To run the app yourself: 

1. Clone or fork the [GitHub repo](https://github.com/cloudinary-devs/ugc-profile-posts).
1. In **app/config/cloudinary.ts**, replace **MY_CLOUD_NAME** with your Cloudinary product environment cloud name.
    
What's my cloud name?

You can find your **Cloud name** near the top of the [Dashboard](https://console.cloudinary.com/app/home/dashboard) of the Cloudinary Console. [Sign up for free](https://cloudinary.com/users/register_free) if you don't yet have a Cloudinary account.

2. [Register](https://console.cloudinary.com/app/settings/addons) for the following add-ons (they both have free tiers):
     * [Amazon Rekognition AI Moderation add-on](aws_rekognition_ai_moderation_addon)
     * [Perception Point Malware Detection add-on](perception_point_malware_detection_addon)
3. To try out your app locally, you need to set up a secure tunnel connecting the internet to your locally running application so that the webhooks sent by Cloudinary on upload are caught and handled by the app. You can use a tool such as [Ngrok](https://ngrok.com/) to do this. Otherwise, you need to deploy the app using a service such as [Vercel](https://vercel.com/). Whichever method you choose, make a note of your app's domain (for example, `a-b-c-d.ngrok-free.app` or `a-b-c-d.vercel.app`). By default, the app runs on port 3000.
4. Create an upload preset called **ugc-profile-photo**. (You can use a different name, but if you do, you also need update the `uploadPreset` value in **cloudinary.ts**.) See instructions on how to [configure your upload preset](#upload_preset_configuration).
5. Ensure that the **Notification URL** in your upload preset is set to:`https://<your app's domain>/api/moderate`
6. Upload an image to use as the default image (for example this image), and set its public ID to `avatar-pic`. Alternatively, use an image that's already in your product environment, and change the value of `defaultImage` in **cloudinary.ts** to its public ID. 
7. If running locally, run:
   
      ```terminal
      npm i
      ```
      then

      ```terminal
      npm run dev
      ```

      Then open [http://localhost:3000](http://localhost:3000) in your browser to see the app running. 

### Upload preset configuration

To configure the upload preset:

1. Log into your [Cloudinary Console](https://console.cloudinary.com). 
1. Navigate to **Settings > Upload > Upload Presets**.
1. Click **Add Upload Preset**. 
1. Configure each of the sections as shown below, then click **Save**:

#### General

{table:class=wide-2ndcol} Parameter | Value | Meaning
--|--|--
 **Upload preset name** | `ugc-profile-photo` | The name of the upload preset. This must match the **uploadPreset** parameter used in the [Upload widget configuration](#upload_widget) (set in **cloudinary.ts**).
 **Signing mode** | `Unsigned` | No signature is required for uploading assets using this upload preset.
 **Auto-generate an unguessable public ID value** | `true` | It's best to generate a random value to avoid conflicts if you have many users uploading their images to your product environment.

The rest of the **General** settings can be set as you like.

![Advanced upload preset settings](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/upload_preset_general_tab.png "thumb: c_scale,w_600/dpr_2.0, width:600, popup:true")

#### Transform

{table:class=wide-2ndcol} Parameter | Value | Meaning
--|--|--
 **Incoming transformation** | `c_limit,h_1000,w_1000/fl_force_strip` | Limit the dimensions of the image to 1000 by 1000 pixels and strip embedded metadata associated with the image. 

![Transform upload preset settings](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/upload_preset_transform.png "thumb: c_scale,w_600/dpr_2.0, width:600, popup:true")

#### Manage and Analyze

{table:class=wide-2ndcol} Parameter | Value | Meaning
--|--|--
 **Retrieve quality analysis data** | `true` | Return quality analysis data in the upload response. 

![Manage and analyze upload preset settings](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/upload_preset_manage_and_analyze.png "thumb: c_scale,w_600/dpr_2.0, width:600, popup:true")

#### Optimize and Deliver

{table:class=wide-2ndcol} Parameter | Value | Meaning
--|--|--
 **Delivery type** | `Upload` | Make the image publicly available.

![Optimize and deliver upload preset settings](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/upload_preset_optimize_and_deliver.png "thumb: c_scale,w_600/dpr_2.0, width:600, popup:true")

#### Advanced

{table:class=wide-2ndcol} Parameter | Value | Meaning
--|--|--
 **Notification URL** | `https://<your app's domain>/api/moderate` | The API endpoint for your app. To try out your app locally, you need to set up a secure tunnel connecting the internet to your locally running application so that the webhooks sent by Cloudinary on upload are caught and handled by the app. You can use a tool such as [Ngrok](https://ngrok.com/) to do this. Otherwise, you need to deploy the app using a service such as [Vercel](https://vercel.com/).
 **Eval script** | `if (resource_info.quality_analysis.focus 

graph TD
    subgraph "Layout"
        L[RootLayout]
        Nav[Navigation]
    end

    subgraph "Context"
        UC[UserContext Provider]
        US[User State]
    end

    subgraph "Pages"
        PP[Profile Page]
        PostP[Posts Page]
    end

    subgraph "Components"
        UW[UploadWidget]
        CLD[Cloudinary Instance]
    end

    MOD[Moderation Endpoint<br/>/api/moderate]
    CS[Cloudinary Service]
-->
[//]: # (L --> UC)
[//]: # (UC --> US)
[//]: # (UC --> PP)
[//]: # (UC --> PostP)
[//]: # (PP --> UW)
[//]: # (PostP --> UW)
[//]: # (UW --> CS)
[//]: # (CS --> MOD)
[//]: # (UW --> MOD)
[//]: # (PP --> CLD)
[//]: # (PostP --> CLD)
[//]: # (CLD --> CS)

-->

##### Project files

The main project files are as follows:

{table:class=no-borders overview align-bullets} File | Functionality
--|--
[app/context/UserContext.tsx](https://github.com/cloudinary-devs/ugc-profile-posts/blob/main/app/context/UserContext.tsx) | Provides global state managementStores user information: profile picture, name, location, birthday, postsMakes user data accessible throughout the application
[app/page.tsx](https://github.com/cloudinary-devs/ugc-profile-posts/blob/main/app/page.tsx) (Profile Page) | Allows users to edit personal informationHandles profile picture uploadsImplements image moderationUses Cloudinary for image optimization and face detection
[app/posts/page.tsx](https://github.com/cloudinary-devs/ugc-profile-posts/blob/main/app/posts/page.tsx) (Posts Page) | Enables users to create posts with text and optional imagesDisplays posts with profile picturesImplements image moderation for uploaded imagesUses Cloudinary for image optimization and transformations
[app/components/upload-widget.tsx](https://github.com/cloudinary-devs/ugc-profile-posts/blob/main/app/components/upload-widget.tsx) | Handles image uploads using Cloudinary's widgetManages upload states and error handlingCommunicates with the moderation APIUsed by both Profile and Posts pages
[app/components/cld.ts](https://github.com/cloudinary-devs/ugc-profile-posts/blob/main/app/components/cld.ts) | Configures the Cloudinary instanceHandles image transformations and optimization
[app/config/cloudinary.ts](https://github.com/cloudinary-devs/ugc-profile-posts/blob/main/app/config/cloudinary.ts) | Contains configuration details specific to the app instance
[app/api/moderate/route.ts](https://github.com/cloudinary-devs/ugc-profile-posts/blob/main/app/api/moderate/route.ts) | Manages image moderation for uploaded filesHandles webhooks from Cloudinary when moderation is completeHandles frontend requests checking the moderation status of an uploaded image

  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
  mermaid.initialize({ 
    startOnLoad: true,
            theme: 'base',
            themeVariables: {
                // Base colors
                primaryColor: '#b4e6e2',
                primaryTextColor: '#2d4f4b',
                primaryBorderColor: '#497f7a',
                lineColor: '#666666',
                
                // Node colors
                mainBkg: '#e6e6e6',
                nodeBorder: '#555555',
                clusterBkg: '#f5f5f5',
                clusterBorder: '#666666',
                
                // Text colors
                titleColor: '#333333',
                nodeTextColor: '#333333',
                
                // Special states
                activeBorderColor: '#0d6efd',
                activeTextColor: '#0d6efd',
                
                // Font settings
                fontFamily: 'system-ui, -apple-system, sans-serif',
                fontSize: '16px',

                noteBkgColor: '#e6e6e6',
                
                // Dark mode overrides using CSS
                darkMode: {
                    mainBkg: '#2b2b2b',
                    nodeBorder: '#aaaaaa',
                    nodeTextColor: '#eeeeee',
                    lineColor: '#999999',
                    titleColor: '#eeeeee',
                    edgeLabelBackground: '#2b2b2b'
                }
            },
            // Additional settings for better readability
            flowchart: {
                curve: 'basis',
                padding: 20,
                useMaxWidth: true
            } });

 -->

{/collapsed}

### Upload preset The upload preset specifies what happens when a user uploads an image. 

In the case of this app, the upload preset:

* Generates a random value for the public ID of any uploaded image
* Applies an incoming transformation to limit the image's dimensions and strip embedded metadata
* Requests quality analysis data
* Makes the image publicly available
* Sets the app's `moderate` endpoint as the notification URL 
* Evaluates the quality of the image on upload, conditionally applying a tag to the uploaded image
* Invokes the **Rekognition AI Moderation** and **Perception Point** add-ons.
   
See how to [configure the upload preset](#upload_preset_configuration).

### Upload widget The Cloudinary [Upload widget](upload_widget) provides a fully functional, configurable, graphical interface for uploading files to Cloudinary. 

In this app, it's used for the profile picture upload in addition to image uploads for posts.

As such, the widget is configured and created in one component, **upload-widget.tsx**, for reusability.

#### Import the script

The Upload widget script is imported in **layout.tsx**:

layout.tsx

```html
<Script src="https://upload-widget.cloudinary.com/global/all.js" strategy="beforeInteractive" />
```

#### Upload widget creation and configuration

The Upload widget is created using the `createUploadWidget` function:

upload-widget.tsx

```React
  const widget = window.cloudinary.createUploadWidget(
    {
      cloudName: CLOUDINARY_CONFIG.cloudName,
      clientAllowedFormats: 'image',
      uploadPreset: CLOUDINARY_CONFIG.uploadPreset,
      sources: ['local'],
      multiple: false,
      maxFiles: 1,
    },
    async (error: any, result: any) => {
      if (error) {
        console.error('Upload error:', error)
        onUploadError(`Upload failed: ${error.statusText || 'Unknown error'}`)
        return
      }

      if (result && result.event === 'success') {
        onUploadError('')
        setLoading(true)
        const startTime = Date.now()
        checkModeration(result.info, startTime)
      } else if (result && result.event === 'close') {
        if (!result.info) {
          onUploadError('Upload cancelled or failed. Please try again.')
        }
      }
    }
  )
```

The configuration parameters are set as follows:

{table:class=wide-2ndcol} Parameter | Value | Meaning
--|--|--
 **cloudName** | `CLOUDINARY_CONFIG.cloudName` | This is the name of product environment to which the images are uploaded. You need to change this for your environment in **cloudinary.ts**.
**clientAllowedFormats** | `image` | Only images are permitted to be uploaded.
**uploadPreset** | `CLOUDINARY_CONFIG.uploadPreset` | This is the name of the upload preset, which defines what happens on upload. It is set in **cloudinary.ts**.  See [Upload preset configuration](#upload_preset_configuration).
**sources** | `['local']` | Only images from the user's local environment can be uploaded. You can change this to allow more [sources](upload_widget_reference#widget_parameters), if required.  
**multiple** | `false` | Only one image can be selected.
**maxFiles** | `1` | Only one image at a time can be uploaded.

The callback function `async (error: any, result: any) => ` handles the upload results:

* If the upload returns an error, then the error is displayed.
* A successful upload clears any displayed errors, activates the loading state (to start the spinner), captures the current timestamp and starts the [moderation check](#moderation_and_malware_detection).

#### Upload widget rendering

The `UploadWidget` component renders a button, which opens the widget in response to a click:

upload-widget.tsx

```React
  const openUploadWidget = (e: React.MouseEvent) => {
    onClick(e)
    if (uploadWidget) {
      uploadWidget.open()
    }
  }

  // Return the button that opens the Upload widget
  return (
    <button
      onClick={openUploadWidget}
      className="bg-green-500 hover:bg-green-600 text-white p-2 rounded transition duration-300"
    >
      {buttonText}
    </button>
  )
```

The `UploadWidget` component is used in **page.tsx** and **posts/page.tsx**:

page.tsx

```react
<UploadWidget
  onUploadSuccess={handleUploadSuccess}
  onUploadError={handleUploadError}
  setLoading={setLoading}
  buttonText="Edit Profile Picture"
  onClick={handleImageUpload}
/>
```

The code is identical for each page, except for the button text.

posts/page.tsx

```react
<UploadWidget
  onUploadSuccess={handleUploadSuccess}
  onUploadError={handleUploadError}
  setLoading={setLoading}
  buttonText="Upload Image"
  onClick={handleImageUpload}
/>
```

The props are specific to the page, so for example, the `handleUploadSuccess` function in `page.tsx` has different functionality to the `handleUploadSuccess`  function in `posts/page.tsx`.

#### Upload widget state management

The `UploadWidget` component primarily manages one piece of state - the Cloudinary Upload widget instance itself:

upload-widget.tsx

```react
const [uploadWidget, setUploadWidget] = useState<CloudinaryWidget | null>(null)
```

This state is handled in few places:

**Initialization** - in the `useEffect`:

upload-widget.tsx

```react
useEffect(() => {
  if (typeof window !== 'undefined' && window.cloudinary) {
    const widget = window.cloudinary.createUploadWidget({
      // config options...
    }, callback)
    setUploadWidget(widget)  // Store widget in state
  }

  // Cleanup
  return () => {
    if (uploadWidget) {
      uploadWidget.destroy()
    }
  }
}, [checkModeration, onUploadError, setLoading])
```

**Usage** - in the click handler:

upload-widget.tsx

```react
const openUploadWidget = (e: React.MouseEvent) => {
  onClick(e)  // Parent's click handler for preventing form submission
  if (uploadWidget) {
    uploadWidget.open()
  }
}
```

**Props for external state management** - the component doesn't manage upload state itself, but instead relies on props to communicate with its parent:

upload-widget.tsx

```react
interface UploadWidgetProps {
  onUploadSuccess: (publicId: string, poorQuality: boolean) => void
  onUploadError: (error: string) => void
  setLoading: (loading: boolean) => void
  buttonText: string
  onClick: (e: React.MouseEvent) => void
}
```

The actual upload status, errors, and loading states are managed by the parent components (pages), making use of the "lifting state up" React pattern - the widget itself manages only what it absolutely needs to (the widget instance), while letting parent components manage the states that might need to be shared or displayed elsewhere in the app:

page.tsx

```react
const [loading, setLoading] = useState(false)
const [uploadError, setUploadError] = useState('')
```

posts/page.tsx

```react
const [uploadError, setUploadError] = useState('')
const [loading, setLoading] = useState(false)
const [newImage, setNewImage] = useState('')
```

### Moderation and malware detection When images are uploaded to Cloudinary, the [Amazon Rekognition AI Moderation](aws_rekognition_ai_moderation_addon) and [Perception Point Malware Detection](perception_point_malware_detection_addon) add-ons are invoked in turn (as requested in the [upload preset](#addons)).

If either of these checks fails, the image is not saved to the product environment, and an error is reported by the app.

#### Moderation flow

The key stages in the moderation flow, as depicted in the diagram below, are:

* User actions trigger the Upload widget
* Images are uploaded to Cloudinary
* Cloudinary processes the moderation and sends webhooks
* Frontend polls the moderation status
* If moderation passes, images are displayed

![Moderation sequence](https://cloudinary-res.cloudinary.com/image/upload/f_auto/q_auto/docs/ugc_profile_pic_moderation_flow_diagram.png "thumb: c_scale,w_700/dpr_2.0, width:700, popup:true")

sequenceDiagram
    participant FE as Frontend
    participant UW as Upload Widget
    participant CLD as Cloudinary
    participant MOD as Moderation API (/api/moderate)
    
    FE->>UW: User initiates upload
    UW->>CLD: Upload image
-->
[//]: # (CLD-->>UW: Return upload success + asset info)
>MOD: Send moderation webhook
        Note over MOD: Store result inmoderationResults Map
    and Frontend to Moderation API
        UW->>MOD: Poll for moderation status
-->
[//]: # (MOD-->>UW: Return pending status)
until result received
    end
-->    
[//]: # (MOD-->>UW: Return final result(approved/rejected))
>FE: Return publicId + quality info
        Note over FE: Display image withappropriate transformations
    else Moderation Rejected
        UW->>FE: Return error message
        Note over FE: Display error to user
    end

-->

#### Catch the webhook from Cloudinary

On completing the moderation and malware checks, Cloudinary sends a webhook notification, which is caught by the backend route handler:

route.ts

```javascript
if (data.notification_type === 'moderation' || data.notification_type === 'moderation_summary') {
  const { moderation_status, moderation_kind, asset_id } = data
  
  let status = 'pending'
  let message = ''
  
  if (moderation_status === 'rejected') {
    status = 'rejected'
    if (moderation_kind === 'aws_rek') {
      message = 'Your image was rejected due to unsuitable content'
    } else if (moderation_kind === 'perception_point') {
      message = 'Your image was rejected due to potential malware'
    }
  }
  // Store result in temporary map
  moderationResults.set(asset_id, { status, message })
}
```

#### Poll the backend endpoint

On successful upload, the `checkModeration` function is called every second to poll the backend endpoint, seeing if a moderation result has been returned for the asset. 

upload-widget.tsx

```react
const checkModeration = useCallback(async (info: any, startTime: number) => {
  // Timeout after 1 minute
  if (Date.now() - startTime > 60000) {
    onUploadError('Moderation check timed out. Please try again.')
    return
  }

  try {
    // Poll the backend endpoint every second
    const response = await fetch('/api/moderate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(info),
    })
    const data = await response.json()
    
    if (data.status === 'approved') {
      onUploadSuccess(data.publicId, data.poorQuality)
    } else if (data.status === 'rejected') {
      onUploadError(data.message)
    } else {
      // If still pending, check again after 1 second
      setTimeout(() => checkModeration(info, startTime), 1000)
    }
  } catch (error) {
    onUploadError('An error occurred while processing your image.')
  }
}, [onUploadSuccess, onUploadError])
```

#### Handle the frontend polling requests

The backend endpoint handles the frontend polling requests to check the moderation status.

If a moderation result is found, a response is returned, with the status set to `approved` or `rejected` as applicable.  If `approved`, the [quality assessment](#quality_analysis) is also performed.

route.ts

```javascript
// If it's not a webhook, it's a request from our frontend
const { public_id, asset_id } = data

// Check if we have a moderation result for this asset
const moderationResult = moderationResults.get(asset_id)

if (!moderationResult) {
  return NextResponse.json({ status: 'pending', message: 'Moderation in progress' })
}

// Clear the result from our temporary storage
moderationResults.delete(asset_id)

if (moderationResult.status === 'approved') {

  let poorQuality = false

  if (data.tags && data.tags.includes('poor_quality')) {
    poorQuality = true
  }

  return NextResponse.json({ 
    status: 'approved',
    poorQuality: poorQuality, 
    publicId: public_id
  })
} else {
  return NextResponse.json({ 
    status: 'rejected', 
    message: moderationResult.message
  })
}

```

### Quality analysis The quality of the uploaded images is assessed and enhancements are made to the profile picture if necessary. 

#### Request quality analysis

In the [upload preset](#manage_and_analyze) configuration, **Retrieve quality analysis data** is set to true. This causes quality analysis data to be returned in the upload response.

#### Evaluate quality

The quality data is evaluated on upload, and if found to be below a threshold, a tag is set on the image: `poor_quality`.

This is invoked through the `eval` parameter, set in [upload preset](#advanced).

`eval` parameter

```javascript
if (resource_info.quality_analysis.focus < 0.7) { 
  upload_options['tags'] = 'poor_quality' 
}
```

#### Handle quality assessment

When checking moderation results, the tags are checked to see if the image was determined to be poor quality, and the API returns a `poorQuality` flag.

route.ts

```javascript
if (moderationResult.status === 'approved') {

  let poorQuality = false

  if (data.tags && data.tags.includes('poor_quality')) {
    poorQuality = true
  }

  return NextResponse.json({ 
    status: 'approved',
    poorQuality: poorQuality, 
    publicId: public_id
  })
} 
```

#### Address quality issues

The quality flag is checked following a successful upload, and if the quality has been flagged as poor, the [enhance](transformation_reference#e_enhance), [generativeRestore](transformation_reference#e_gen_restore) and [upscale](transformation_reference#e_upscale) effects are applied to the profile picture.

* **enhance()**: Applies automatic image quality enhancement
* **generativeRestore()**: Uses AI to restore and improve image quality
* **upscale()**: Increases resolution while maintaining quality and works particularly well on faces

page.tsx

```react
if (poorQuality) {
  profileImage.effect(enhance()).effect(generativeRestore()).effect(upscale())
}
```

The flag is also saved to the `UserContext`, so the same effects can be applied to the profile pictures on the Posts page.

posts/page.tsx

```react
if (profileIsPoorQuality) {
  img.effect(enhance()).effect(generativeRestore()).effect(upscale())
}
```

### Transformations and optimizations #### Configure the Cloudinary instance

The Cloudinary instance, required for the transformations, is created in **cld.ts**. The cloud name itself is set in the **cloudinary.ts** configuration file.
   

cld.ts

```javascript
import { Cloudinary } from "@cloudinary/url-gen"
import { CLOUDINARY_CONFIG } from "../config/cloudinary"

// Create a Cloudinary instance for the product environment.
const cld = new Cloudinary({
  cloud: {
    cloudName: CLOUDINARY_CONFIG.cloudName
  }
})

export default cld
```

cloudinary.ts

```javascript
export const CLOUDINARY_CONFIG = {
    cloudName: 'MY_CLOUD_NAME',
    uploadPreset: 'ugc-profile-photo',
    defaultImage: 'avatar-pic'
  } as const
```

> **INFO**: Remember to change `MY_CLOUD_NAME` to your own cloud name.

#### Profile picture

On the Profile page, the profile picture is resized to a width and height of 300 pixels. If the picture is not already square, then it is cropped. In order to ensure the face is kept in the crop, the `gravity` is set to focus on the face (`focusOn(face())`). In case there isn't a face in the image, the fallback gravity is set to `auto` (the most interesting part of the image as determined by AI) (`fallbackGravity(autoGravity())`).

Both format and quality are also set to `auto`, which means the image is delivered to the browser in the most [optimal format](image_optimization#how_to_optimize_image_format) and with the most [optimal compression](image_optimization#how_to_optimize_image_quality) applied.

If the `poorQuality` flag is set, [quality enhancement transformations](#quality_analysis) are also applied.

page.tsx

```react
const profileImage = cld.image(publicId)

profileImage.resize(fill().width(300).height(300).gravity(
  focusOn(face()).fallbackGravity(autoGravity())))
  .format('auto').quality('auto')

if (poorQuality) {
  profileImage.effect(enhance()).effect(generativeRestore()).effect(upscale())
}
```

The full URL generated for the profile picture, including quality enhancements, is:

![URL of the profile picture](https://res.cloudinary.com/demo/image/upload/c_fill,g_face:auto,h_300,w_300/f_auto/q_auto/e_enhance/e_gen_restore/e_upscale/docs/profile_pic_1000 "with_image:false, with_code:false")

Original image

Transformed image 

#### Profile pictures on Posts page

On the Posts page, the profile image has enhancements applied if applicable, so it resembles the picture on the Profile page, then it's resized to 75 x 75 pixels, using the same gravity as for the [profile picture](#profile_picture). Its corners are rounded to the maximum, forming a circle (`roundCorners(max())`), and a pink outline is applied (`effect(outline().color("pink"))`).  

Both format and quality are also set to `auto`, which means the image is delivered to the browser in the most optimal format and with the most optimal compression applied.

posts/page.tsx

```react
const img = cld.image(profilePublicId || CLOUDINARY_CONFIG.defaultImage)
if (profileIsPoorQuality) {
  img.effect(enhance())
  .effect(generativeRestore())
  .effect(upscale())
}
img.resize(fill().width(75).height(75).gravity(
  focusOn(face()).fallbackGravity(autoGravity())))
.roundCorners(max())
.effect(outline().color("pink"))
.format('auto')
.quality('auto')
```

The full URL generated for the profile picture, including quality enhancements, is:

![URL of the profile picture](https://res.cloudinary.com/demo/image/upload/e_enhance/e_gen_restore/e_upscale/c_fill,g_face:auto,h_75,w_75/r_max/co_pink,e_outline/f_auto/q_auto/docs/profile_pic_1000 "with_image:false, with_code:false")

Original image

Transformed image 

#### Post images

The image uploaded in a post is displayed in a 300 x 200 pixel rectangle, padded with a gray background and with rounded corners.

posts/page.tsx

```react
cldImg = { 
  cld.image(post.image).
    resize(pad().width(300).height(200).background(color("gray")))
    .roundCorners(byRadius(5))
    .format('auto')
    .quality('auto')
} 
```

The full URL generated for a posted picture is:

![Post image, padded with a gray background](https://res.cloudinary.com/demo/image/upload/b_gray,c_pad,h_200,w_300/r_5/f_auto/q_auto/docs/prod-shoes2.jpg "with_code:false, width:300")

#### The AdvancedImage component

All images from this app are delivered using the React SDK [AdvancedImage component](react_image_transformations#image_transformations_with_react).

For example:

page.tsx

```react
profileImage ? (
  <AdvancedImage cldImg={profileImage} width={300} height={300} alt="Profile"/>

) : (
  <AdvancedImage cldImg={cld.image(CLOUDINARY_CONFIG.defaultImage).
    resize(fill().width(300).height(300).gravity(autoGravity())).format('auto').quality('auto')} width={300} height={300} alt="Profile" />
)
```

> **NOTE**: This app demonstrates the Cloudinary [React SDK](react_integration), but you may also want to try out the community-developed [Next.js SDK](community_sdks#next_js_components).