Create Dynamic Screenshots of Websites

Ifeoma Imoh

Embedding URL screenshots into a web application the traditional way be stressful and time-consuming. This is because you have to visit the URL, install a screenshot capture solution that takes the screenshot, and maybe edit it using a graphic editor before embedding it into the application.

Among the wide range of media services Cloudinary provides is the URL2PNG add-on. This screenshots-as-a-service helps generate screenshots of URLs on-the-fly and makes the whole editing and transformation process fast and easy. A common use of this service may be seen in blogs and popular news sites, where screenshots of websites that have been shared are embedded into posts. For example, Google and other popular search engines like Bing and Yahoo! display screenshots of websites in search results.

In this article, we'll walk through setting up Cloudinary's URL2PNG add-on and generating static screenshots of websites using a simple Next.js application. Let's begin!

Here is a link to the demo on CodeSandbox.

Project Setup

Let’s create a new Next.js application by running this command:

1npx create-next-app url-to-png

The create-next-app is a Next.js bootstrapping tool, which helps avoid the hassle of manually setting up a Next.js application. Once that’s done installing, you can cd into your directory using this command:

1cd url-to-png

Now run this command in your terminal to install the dependencies we need for this project.

1npm install cloudinary axios dotenv

We will be using the Cloudinary node SDK to generate screenshots, which will be sent to the frontend, while axios is the HTTP client we will use to communicate with our serverless function.

Before proceeding to the next section, let's create a file called getImage.js inside the api folder, which will hold our serverless function. We won’t be creating a separate folder for components, as most of the components in our application will be contained in the Home component in the index.js file.

Setting up Cloudinary

We need to complete a few steps before we can start enjoying the prowess offered by the Cloudinary URL2PNG add-on. Click here to create an account with Cloudinary, or login if you already have one.

You should see a dashboard after a successful signup or login. The dashboard displays your Cloudinary credentials. Let's set up our environment variables. Create a root-level file called .env and add the following variable to it:


Next, we need to register for the Cloudinary URL2PNG add-on before using the tool. Click on the Add-ons navigation item in the navigation bar.

You should see a page with a list of all the available add-ons; scroll up to locate the URL2PNG add-on and click it.

It is also important to note that this add-on has different subscription plans with various offers. Of course, we will be using the free plan in this application, and it gives us 50 screenshots per month. However, you can subscribe to any plan based on your needs by clicking the preferred link.

Demystifying the Application

Our application will comprise mainly of two major components. The index.js file holds the frontend code for getting the URL and rendering the screenshots, and then the getImage.js file contains the backend logic for configuring Cloudinary and generating the screenshots.

Add the following to the index.js file in /pages/ directory:

1import { useState } from 'react';
2 import styles from '../styles/Home.module.css';
3 import axios from 'axios';
4 export default function Home() {
5 //.....
6 return <div className={styles.main}></div>;
7 }

In the code above, we imported the React useState() hook, which will be used to hold stateful values. We also imported styles from the Home.module.css file using the CSS modules approach to styling. Lastly, we brought in axios, which will be used to communicate with our serverless function.

For the backend, add the following to the getImage.js file in the /pages/api/ directory:

1const cloudinary = require('cloudinary').v2;
2 cloudinary.config({
3 //...........
4 });
5 export default function handler(req, res) {
6 //.........
7 }

After abstractly defining the components of our application, we’ll proceed in the following sections by going over these steps to implement the Cloudinary URL2PNG add-on in the application.

  • Develop the user interface and get the user’s URL
  • Create the logic to generate screenshots
  • Send the URL to the backend and embed the screenshot

Develop the User Interface

Add the following to your index.js file in the /pages/ directory:

1import { useState } from 'react';
2 import Image from 'next/image';
3 import styles from '../styles/Home.module.css';
4 import axios from 'axios';
5 export default function Home() {
6 const [screenShotUrl, setScreenShotUrl] = useState('');
7 const [url, setUrl] = useState('');
8 const handleOnGenerate = () => {
9 console.log('Yes, it worked');
10 };
11 const setInputUrl = (e) => {
12 setUrl(;
13 };
14 return (
15 <div className={styles.main}>
16 <input
17 type="text"
18 onChange={setInputUrl}
19 value={url}
20 placeholder="Enter Url or paste Url"
21 ></input>
22 <button onClick={handleOnGenerate}>Generate Snapshot</button>
23 <div className={styles.imageWrapper}>
24 {!!screenShotUrl ? <img src={screenShotUrl} alt="screenshot" /> : null}
25 </div>
26 </div>
27 );
28 }

We created two state variables, url and screenShotUrl, to store the URL entered by the user and the generated screenshot link, respectively. We then rendered a controlled input element and set its value to the value stored in the url state variable.

We also returned a button with a yet-to-be-defined handleOnGenerate function and a wrapper div to hold the generated screenshot.

To conclude this section, we need to add some styles to our application. Replace the styles in your Home.module.css file in the /styles/ directory with the following:

1.main {
2 display: flex;
3 flex-direction: column;
4 width: 800px;
5 max-width: 80%;
6 align-items: center;
7 margin: 4rem auto;
8 }
9 .main > input {
10 width: 100%;
11 padding: 0.6rem 1rem;
12 border: 1px solid rgba(0,0,0,.1);
13 margin: 1rem 0;
14 }
15 .main > input:focus {
16 outline: none;
17 }
18 .main > button {
19 padding: 0.6rem 1rem;
20 background: green;
21 border: none;
22 color: #fff;
23 font-size: 1rem;
24 font-family: monospace;
25 cursor: pointer;
26 }
27 .imageWrapper {
28 width: 100%;
29 height: 50vh;
30 border: 1px solid rgba(0,0,0,.1);
31 margin: 2rem 0;
32 }
33 .imageWrapper > img {
34 width: 100%;
35 object-fit: cover;
36 }

Create Logic to Generate Screenshots

Open your getImage.js file, and update its content with the following:

1const cloudinary = require('cloudinary').v2;
2 require('dotenv').config();
4 cloudinary.config({
5 cloud_name: process.env.CLOUD_NAME,
6 api_key: process.env.API_KEY,
7 api_secret: process.env.API_SECRET,
8 secure: true,
9 });
11 export default function handler(req, res) {
12 const imageShot = cloudinary.image(req.body.url, {
13 type: 'url2png',
14 sign_url: true,
15 });
16 res.status(200).json({ name: imageShot });
17 }

We set the configuration parameters using our Cloudinary credentials. We created a constant called imageShot that holds the generated screenshot link, which is then sent as a JSON object response.

Send the URL to the Backend and Embed the Screenshot

Update your index.js file with the following code:

1import { useState } from 'react';
2 import Image from 'next/image';
3 import styles from '../styles/Home.module.css';
4 import axios from 'axios';
5 export default function Home() {
6 const [screenShotUrl, setScreenShotUrl] = useState('');
7 const [url, setUrl] = useState('');
8 const getImageEl = async () => {
9 const resp = await'/api/getImage', {
10 url: url,
11 });
12 const responseUrl = /'(.+)'/.exec(;
13 setScreenShotUrl(responseUrl[1]);
14 };
15 const handleOnGenerate = () => {
16 getImageEl();
17 };
18 // Remove this function
19 const handleOnGenerate = () => {
20 console.log('Yes, it worked');
21 };
22 const setInputUrl = (e) => {
23 setUrl(;
24 };
25 return (
26 <div className={styles.main}>
27 //...
28 </div>
29 );
30 }

The code adds an a getImageEl() function that is called when the handleOnGenerate() get triggered. getImageEl() is an asynchronous function that makes an HTTP post request to the serverless function, while passing the URL as payload.

A regular expression is then executed on the response data to extract the generated screenshot link, which is then stored in state. We also embedded the generated screenshot into our application by passing the link generated as the src value to an img tag.

The gif below shows the demo:

You can also add some transformations to the screenshots generated using conventional Cloudinary techniques. To learn about adding transformation to screenshots, click here.

Find the complete project here on GitHub.


The Cloudinary URL2PNG add-on offers great URL screenshot generation and transformation capabilities that are remarkably better than the traditional way of doing it and offer excellent flexibility in integrating with various applications.

Resources You May Find Useful

Ifeoma Imoh

Software Developer

Ifeoma is a software developer and technical content creator in love with all things JavaScript.