Transform Documents into Images using Next.js APIs

Demola Malomo

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.

Technology Overview

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.

Sandbox

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

Prerequisites

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)

Getting started

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

1npx 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:

1npm 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:

1NEXT_PUBLIC_CLOUD_NAME=/*CLOUD NAME HERE/*
2NEXT_PUBLIC_API_KEY=/*UPLOAD API KEY HERE/*
3NEXT_PUBLIC_API_SECRET=/*UPLOAD API SECRET HERE/*

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.

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

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.

Creating a Word Document Transform Application

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:

1.container {
2 padding: 0 2rem;
3}
4
5.main {
6 min-height: 100vh;
7 padding: 4rem 0;
8 flex: 1;
9 display: flex;
10 flex-direction: column;
11 justify-content: center;
12 align-items: center;
13}
14
15.section {
16 display: grid;
17 grid-template-columns: 2fr;
18}
19
20.label {
21 display: block;
22 margin-bottom: 0.6rem;
23}
24
25.fileUpload {
26 border: 1px solid #ccc;
27 display: inline-block;
28 padding: 6px 12px;
29 margin-bottom: 0.6rem;
30 width: 100%;
31}
32
33.description {
34 margin: 2rem 0;
35 line-height: 1.5;
36 font-size: 1.5rem;
37}
38
39.img {
40 margin-top: 6rem;
41}
42
43.button {
44 display: block;
45 border: none;
46 padding: 0.6rem 2rem;
47 margin: 1rem 0rem;
48 background-color: blue;
49 color: white;
50 cursor: pointer;
51}

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:

1import cloudinary from 'cloudinary';
2import multer from 'multer';
3
4export const config = {
5 api: {
6 bodyParser: false,
7 },
8};
9
10const storage = multer.diskStorage({
11 destination: './public/uploads',
12 filename: (req, file, cb) => cb(null, file.originalname),
13});
14
15const upload = multer({
16 storage,
17});
18
19export default async function convert(req, res) {
20 const cld = cloudinary.v2;
21
22 cld.config({
23 cloud_name: process.env.NEXT_PUBLIC_CLOUD_NAME,
24 api_key: process.env.NEXT_PUBLIC_API_KEY,
25 api_secret: process.env.NEXT_PUBLIC_API_SECRET,
26 });
27
28 const { url } = req.query;
29
30 //set waiting time
31 const wait = () => {
32 return new Promise((resolve, reject) => {
33 setTimeout(resolve, 5000);
34 });
35 };
36
37 upload.single('file')(req, {}, (err) => {
38 const filePath = `./public/uploads/${req.file.originalname}`;
39
40 cld.uploader
41 .upload(filePath, {
42 resource_type: 'raw',
43 raw_convert: 'aspose',
44 notification_url: `http://${url}`,
45 })
46 .then(async (result) => {
47 let state = null;
48
49 if (result.info.raw_convert.aspose.status === 'pending') {
50 //check if status is successful
51 while (state !== 'success') {
52 await wait();
53 return cld.api
54 .resource(
55 result.public_id,
56 { resource_type: 'raw' },
57 function (error, result) {
58 if (result) {
59 return result;
60 } else {
61 return error;
62 }
63 }
64 )
65 .then((_) => {
66 state = 'success';
67
68 return res.status(200).json({
69 msg: 'document converted successfully!',
70 converted: cld.url(`${result.public_id}.jpeg`, {
71 transformation: [{ width: 600, crop: 'scale' }],
72 }),
73 });
74 })
75 .catch((_) => {
76 return res.status(500).json({ msg: 'error' });
77 });
78 }
79 } else if (result.info.raw_convert.aspose.status !== 'pending') {
80 return res.status(200).json({
81 msg: 'document converted successfully!',
82 converted: cld.url(`${result.public_id}.jpeg`, {
83 transformation: [{ width: 600, crop: 'scale' }],
84 }),
85 });
86 } else {
87 return res.status(500).json({ msg: 'error' });
88 }
89 })
90 .catch((error) => {
91 console.log(error);
92 return res.status(500).json({ msg: error });
93 });
94 });
95}

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:

1import Head from 'next/head';
2import Image from 'next/image';
3import { useState } from 'react'; //add
4import styles from '../styles/Home.module.css';
5
6export default function Home() {
7 const [file, setFile] = useState(null);
8 const [imgSrc, setImgSrc] = useState(null);
9 const [isLoading, setIsLoading] = useState(false);
10
11 const handleSubmit = (e) => {
12 e.preventDefault();
13 setIsLoading(true);
14
15 const formData = new FormData();
16 formData.append('file', file);
17
18 fetch(`/api/convert/${window.location.host}`, {
19 method: 'POST',
20 body: formData,
21 })
22 .then((res) => res.json())
23 .then((res) => {
24 setImgSrc(res.converted);
25 setIsLoading(false);
26 })
27 .catch((_) => {
28 alert('File Upload error!');
29 });
30 };
31
32 return (
33 <div className={styles.container}>
34 <Head>
35 <title>Create Next App</title>
36 <meta name='description' content='Generated by create next app' />
37 <link rel='icon' href='/favicon.ico' />
38 </Head>
39 <main className={styles.main}>
40 <p className={styles.description}>Word To Image</p>
41 <section className={styles.section}>
42 <form method='post' onSubmit={handleSubmit}>
43 <label htmlFor='img' className={styles.label}>
44 Select image:
45 </label>
46 <input
47 type='file'
48 name='img'
49 accept='.doc, .docx'
50 required
51 multiple
52 className={styles.fileUpload}
53 onChange={(e) => setFile(e.target.files[0])}
54 />
55 <button className={styles.button} disabled={isLoading}>
56 {isLoading ? 'Converting' : 'Submit'}
57 </button>
58 </form>
59
60 {imgSrc && (
61 <Image
62 src={imgSrc}
63 height={350}
64 width={350}
65 alt='uploads'
66 className={styles.img}
67 />
68 )}
69 </section>
70 </main>
71 </div>
72 );
73}

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:

1npm run dev

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

Conclusion

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:

Demola Malomo

Software Engineer & Technical Writer

Demola is a software developer, technical writer, and product designer. He has a passion for designing and building scalable applications.