Skip to content

Transform Documents into Images using Next.js APIs

A Word document is an acceptable format for creating documents like letters and reports, including color, clip art, images, videos, or plain words. Over the years, Word documents have become one of the acceptable methods of conveying digital information. But, even with the advancement and adoption, it is still prone to compatibility issues, version history sharing, and metadata sharing, among others.

In this post, we will learn how to transform Word documents to images into a Next.js application using Cloudinary and Aspose.

Cloudinary is a visual media service we use to upload, store, manage, transform, and deliver images and videos for websites and applications.

Aspose is a technology company offering sets of file format APIs that developers can leverage to create, edit, or convert complex to simple file formats.

Cloudinary ships with Aspose as an add-on; part of the image management pipeline, and further extends the upload and transformation capabilities with options to convert Word documents to PDFs and images.

GitHub repository can be found here.

We completed this project in a CodeSandbox. Fork it to run the code.

To fully grasp the concepts presented in this tutorial, the following requirements apply:

  • Basic understanding of JavaScript and React
  • Cloudinary account (Create an account here)

We need to create a Next.js starter project by navigating to the desired directory and running the command below in our terminal.

    npx create-next-app word-to-img && cd word-to-img

The command creates a Next.js project called word-to-img and navigates into the project directory.

We proceed to install the required dependencies by running the command below:

    npm install cloudinary multer

multer is a library for handling file uploads.

Configuring Cloudinary Next, we need to log into our Cloudinary dashboard, copy Cloudinary’s cloud name, API key, and API secret, and include it in our application. To do this, first, we need to create a .env file in the root directory and in this file, add the snippet below:


Cloud Name, API Key, and Secret Highlighted

Enable Cloudinary’s Aspose Add-on To enable the add-on, we need to navigate to the Add-ons tab and click on the Aspose option.

Click on Aspose Option

Select the Free plan and Agree to activate the add-on.

Select Free Plan Agree to Plan

PS: For this demo, we selected the Free plan; however, Cloudinary ships with scalable plans to meet medium to enterprise need**s at an affordable rate.

With that done, we can leverage Next.js CSS Module support to style our page by replacing the content in Home.module.css in the styles folder with the code snippet below:


.container {
  padding: 0 2rem;

.main {
  min-height: 100vh;
  padding: 4rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

.section {
  display: grid;
  grid-template-columns: 2fr;

.label {
  display: block;
  margin-bottom: 0.6rem;

.fileUpload {
  border: 1px solid #ccc;
  display: inline-block;
  padding: 6px 12px;
  margin-bottom: 0.6rem;
  width: 100%;

.description {
  margin: 2rem 0;
  line-height: 1.5;
  font-size: 1.5rem;

.img {
  margin-top: 6rem;

.button {
  display: block;
  border: none;
  padding: 0.6rem 2rem;
  margin: 1rem 0rem;
  background-color: blue;
  color: white;
  cursor: pointer;
Code language: CSS (css)

Next, we need to navigate to the api folder inside the pages folder to create a dynamic route. To do this, create a convert folder, create a [url].js file, and add the snippet below:

import cloudinary from 'cloudinary';
import multer from 'multer';

export const config = {
  api: {
    bodyParser: false,

const storage = multer.diskStorage({
  destination: './public/uploads',
  filename: (req, file, cb) => cb(null, file.originalname),

const upload = multer({

export default async function convert(req, res) {
  const cld = cloudinary.v2;

    cloud_name: process.env.NEXT_PUBLIC_CLOUD_NAME,
    api_key: process.env.NEXT_PUBLIC_API_KEY,
    api_secret: process.env.NEXT_PUBLIC_API_SECRET,

  const { url } = req.query;

  //set waiting time
  const wait = () => {
    return new Promise((resolve, reject) => {
      setTimeout(resolve, 5000);

  upload.single('file')(req, {}, (err) => {
    const filePath = `./public/uploads/${req.file.originalname}`;

      .upload(filePath, {
        resource_type: 'raw',
        raw_convert: 'aspose',
        notification_url: `http://${url}`,
      .then(async (result) => {
        let state = null;

        if ( === 'pending') {
          //check if status is successful
          while (state !== 'success') {
            await wait();
            return cld.api
                { resource_type: 'raw' },
                function (error, result) {
                  if (result) {
                    return result;
                  } else {
                    return error;
              .then((_) => {
                state = 'success';

                return res.status(200).json({
                  msg: 'document converted successfully!',
                  converted: cld.url(`${result.public_id}.jpeg`, {
                    transformation: [{ width: 600, crop: 'scale' }],
              .catch((_) => {
                return res.status(500).json({ msg: 'error' });
        } else if ( !== 'pending') {
          return res.status(200).json({
            msg: 'document converted successfully!',
            converted: cld.url(`${result.public_id}.jpeg`, {
              transformation: [{ width: 600, crop: 'scale' }],
        } else {
          return res.status(500).json({ msg: 'error' });
      .catch((error) => {
        return res.status(500).json({ msg: error });
Code language: JavaScript (javascript)

The snippet above does the following:

  • Imports the required dependencies
  • Creates a config object to disable bodyParser, a Next.js default setting. This option lets us consume the request body in its raw format
  • Instantiates and configures Multer by specifying the upload folder and the filename
  • Instantiates and configures Cloudinary
  • Extracts the request query
  • Creates a wait function to set a delay of 5 seconds
  • Uses the upload.single method to process the selected file. The method also does the following:
    • Uses the uploader.upload function to upload the file to Cloudinary by referencing the relative path, the resource type, Aspose as an add-on, and uses the extracted request query as the notification URL. The notification URL is used to monitor the status of the transformation
    • Conditionally checks the uploaded file’s status at an interval of 5 seconds using the wait function, confirms if it exists using Cloudinary’s api.resource function, and returns the correct response

PS: The uploaded files should be deleted periodically in a production environment to avoid bloating the server. Also, methods like Event Emitter and Webhooks are used to monitor the notification URL. However, we used a while loop to achieve this.

Finally, we modify the index.js file in the pages folder to the following:

import Head from 'next/head';
import Image from 'next/image';
import { useState } from 'react'; //add
import styles from '../styles/Home.module.css';

export default function Home() {
  const [file, setFile] = useState(null);
  const [imgSrc, setImgSrc] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  const handleSubmit = (e) => {

    const formData = new FormData();
    formData.append('file', file);

    fetch(`/api/convert/${}`, {
      method: 'POST',
      body: formData,
      .then((res) => res.json())
      .then((res) => {
      .catch((_) => {
        alert('File Upload error!');

  return (
    <div className={styles.container}>
        <title>Create Next App</title>
        <meta name='description' content='Generated by create next app' />
        <link rel='icon' href='/favicon.ico' />
      <main className={styles.main}>
        <p className={styles.description}>Word To Image</p>
        <section className={styles.section}>
          <form method='post' onSubmit={handleSubmit}>
            <label htmlFor='img' className={styles.label}>
              Select image:
              accept='.doc, .docx'
              onChange={(e) => setFile([0])}
            <button className={styles.button} disabled={isLoading}>
              {isLoading ? 'Converting' : 'Submit'}

          {imgSrc && (
Code language: JavaScript (javascript)

The snippet above does the following:

  • Imports required dependency
  • Creates state to manage the uploads (files, imgSrc, and isLoading)
  • Creates a handleSubmit function for uploading the file by calling the /api/convert/ API we created earlier with the current URL as a query
  • Markup to for the upload form and conditionally show transformed

With that done, we can start a development server using the command below:

    npm run dev

Form Form with Transformed Image

We can also validate the upload and transformation by checking Cloudinary.

Upload and Transformation

This post discussed transforming Word documents into images in a Next.js application using Cloudinary and Aspose. The Cloudinary platform ships with a robust Add-on to manage media and file assets that developers can leverage to build solutions without spending more time programming complex file formats.

These resources might be helpful:

Back to top

Featured Post