What’s more popular than landing your next tech job? Nothing. And even then, how are you sure you’re doing the right thing? Crafting excellent resumes for your different job interests can be a daunting task without some extra help or automation. In this post, we’ll walk through the process of generating a stellar resume on-demand (PDF) with Cloudinary by supplying only the necessary details via a Next.js form.
Cloudinary offers an end-to-end solution for all our image and video needs, including the upload, storage, administration, transformation, and optimized delivery.
Next.js is a React-based frontend development framework that supports server-side rendering and static site generation.
Prerequisites
This post requires the following:
- Experience with JavaScript and react.js
- Installation of Node.js
- A Cloudinary account. Signup is free!
Getting started
We start by downloading this Canva resume template, and uploading the PDF file to Cloudinary.
Log in to Cloudinary and follow the steps below to upload the file.
- Click on "Media Library" from the dashboard.
- Click on the Upload button.
- In the dialog box, navigate to "My Files," drag and drop the PDF file or browse to its directory to upload them.
After successful upload we should note the file "Public-Id."; we'll be using it later.
Setting up Next.js application
Run the following command in the terminal to create a new Next.js application:
1npx create-next-app resume-generator
The above command creates a starter next.js application in the resume-generator folder.
Next, we'll navigate into the project directory and start the application with the following commands:
1cd resume-generator # to navigate into the project directory2npm run dev # to run the dev server
Next.js will start a live development server at http://localhost:3000.
Building the Resume Generator
We'll start by installing the @cloudinary/url-gen package with the following command:
1npm install @cloudinary/url-gen #to access cloudinary SDK in react environment
Next, in the root directory of our project, let’s create a components folder and Form.js
file with the following snippets:
1//components/Form.js2 function Form({ userDetails, setUserDetails, handleSubmit }) {3 const {4 firstname,5 lastname,6 email,7 mobile,8 website,9 course,10 role,11 company,12 position,13 school,14 country,15 state,16 interest_1,17 interest_2,18 interest_3,19 } = userDetails;2021 const handleChange = (e) => {22 setUserDetails({ ...userDetails, [e.target.name]: e.target.value });23 };2425 return (26 //form inputs & Submit button here27 );28 }29 export default Form;
Here:
- We destructured
userDetails
,setUserDetails
, andhandleSubmit
props that we'll pass to this component later in the index.js file. - Destructured user information from the
userDetails
and created thehandleChange()
function using thesetUserDetails
prop; this function will update the user information with the form input values.
In the return()
function of the Form.js
component, we:
- Created the form to collect the user information and created a Submit button to submit the information.
- Passed the
handleChange()
function and thehandleSubmit()
function to the form input and the Submit button, respectively
The complete snippets of the Form.js
component are in this GitHub Gist.
Next, let's clean up the index.js
file and update it with the following snippets:
1//pages/index.js2 import Head from "next/head";3 import { useState } from "react";4 import styles from "../styles/Home.module.css";5 import Form from "../components/Form";67 const initialState = {8 email: "",9 phone: "",10 role: "",11 school: "",12 // init more user details (website, course, etc)13 };14 export default function Home() {15 const [userDetails, setUserDetails] = useState(initialState);16 const [showPDF, setShowPDF] = useState(false);1718 const handleSubmit = async (e) => {19 e.preventDefault();20 setShowPDF(true);21 };2223 return (24 // return statement here25 );26 }
In the above snippets, we:
- Imported
useState
from “react” andForm
from the components folder. - Created
initialState
object that houses the information expected from a user. - Created
userDetails
andshowPDF
constants and used theuseState
hook to set their values toinitialState
andfalse
, respectively. - Created
handleSubmit()
function that will update the value of theshowPDF
to true.
Next, let’s set up the return()
statement in our index.js
file to render the form on the browser:
1import Head from "next/head";2 import { useState } from "react";3 import styles from "../styles/Home.module.css";4 import Form from "../components/Form";56 const initialState = {7 // Init user details here8 };9 export default function Home() {10 const [userDetails, setUserDetails] = useState(initialState);11 const [showPDF, setShowPDF] = useState(false);1213 // handleSubmit() function here14 return (15 <div className={styles.container}>16 <Head>17 <title>Resume Generator</title>18 <meta name="description" content="Generated by create next app" />19 <link rel="icon" href="/favicon.ico" />20 </Head>21 <main className={styles.main}>22 <Form23 userDetails={userDetails}24 setUserDetails={setUserDetails}25 handleSubmit={handleSubmit}26 />27 </main>28 </div>29 );30 }
Here, we gave the application a title, rendered the Form
we imported from the components folder, and passed userDetails
, setUserDetails
, and handleSubmit
as props to it.
In the browser, we should have the application working like below:
Now let’s fetch the resume template we saved earlier and use Cloudinary transformations to overlay the user information on the template.
To achieve that, create a Template.js
file inside the components folder with the following snippets.
1// components/template.js2 import { Cloudinary } from "@cloudinary/url-gen";3 import { source } from "@cloudinary/url-gen/actions/overlay";4 import { text } from "@cloudinary/url-gen/qualifiers/source";5 import { TextStyle } from "@cloudinary/url-gen/qualifiers/textStyle";6 import { Position } from "@cloudinary/url-gen/qualifiers";7 import { compass } from "@cloudinary/url-gen/qualifiers/gravity";8 function Home({ userDetails, setUserDetails, setShowPDF, initialState }) {9 const {10 firstname,11 lastname,12 email,13 mobile,14 website,15 // init more user details (school, course, etc)16 } = userDetails;1718 const cld = new Cloudinary({19 cloud: {20 cloudName: "CLOUD_NAME",21 },22 });2324 const template = cld25 .image("YOUR_IMAGE_URL")26 .addFlag("rasterize");2728 return ();29 }30 export default Home;
In the above snippets, we:
- Imported
Cloudinary
and some action qualifiers from “@cloudinary/url-gen.” - Destructured user information from the
userDetails
prop we’ll pass when rendering theTemplate.js
component in theindex.js
file. - Instantiated a new
Cloudinary
instance with ourcloudName
, saved theCloudinary
instance astemplate
constant, and passed the file public_id we held earlier.
Next, let’s overlay the user information on the PDF file using Cloudinary
transformation.
1// components/template.js2 template.overlay(3 source(4 text(`${firstname} ${lastname}`, new TextStyle("Nunito", 60)).textColor(5 "black"6 )7 ).position(8 new Position().gravity(compass("north")).offsetY(200).offsetX(-400)9 )10 );11 //Education Overlay12 template13 .overlay(14 source(text(`${school}`, new TextStyle("Times", 30))).position(15 new Position().gravity(compass("west")).offsetY(50).offsetX(100)16 )17 )18 .overlay(19 source(20 text(`${course}`, new TextStyle("Times", 30)).textColor("gray")21 ).position(22 new Position().gravity(compass("west")).offsetY(120).offsetX(100)23 )24 );
Here, we overlaid the user’s firstname, lastname, and education information onto the template directly from Cloudinary. Next, lets overlay the user’s contact and profile informations:
1// Contacts Overlay2 template3 .overlay(4 source(5 text(`${mobile}`, new TextStyle("Times", 30)).textColor("gray")6 ).position(7 new Position().gravity(compass("west")).offsetY(410).offsetX(100)8 )9 )10 .overlay(11 source(12 text(`${email}`, new TextStyle("Times", 30)).textColor("gray")13 ).position(14 new Position().gravity(compass("west")).offsetY(510).offsetX(100)15 )16 )17 .overlay(18 source(19 text(`${website}`, new TextStyle("Times", 30)).textColor("gray")20 ).position(21 new Position().gravity(compass("west")).offsetY(610).offsetX(100)22 )23 );24 // Profile Overlays25 template26 .overlay(27 source(28 text(29 `I'm ${firstname} ${lastname},`,30 new TextStyle("Times", 40)31 ).textColor("gray")32 ).position(33 new Position().gravity(compass("north")).offsetY(300).offsetX(200)34 )35 )36 .overlay(37 source(38 text(39 `a ${role} from ${state}, ${country}.`,40 new TextStyle("Times", 30)41 ).textColor("gray")42 ).position(43 new Position().gravity(compass("north")).offsetY(350).offsetX(200)44 )45 );
Repeat this process and overlay the user’s work experience information and any other interests onto the resume template. With all the necessary information overlaid onto the template, our resume is complete. Next, let’s transform it into a downloadable Image that users can access via a URL:
1const pdfTemplate = template.toURL();2 return (3 <div>4 <iframe src={pdfTemplate} width="500" height="778"></iframe>5 <div className="mt-3">6 <button7 onClick={() => {8 setUserDetails(initialState);9 setShowPDF(false);10 }}11 className="btn btn-primary"12 >13 Generate New Resume14 </button>15 </div>16 </div>17 );
Here, we:
- Transformed the
template
to a URL with thetoURL()
Cloudinary function and displayed thetemplate
inreturn()
function. - Created a Generate New Resume button that clears the form so a user can generate a new resume.
We can find the complete snippets of the Template.js
in this Github Gist.
Next, let's import and render the Template.js
component inside the index.js
page and pass the userDetails
to it. Update the index.js
like in the below:
1// Other imports here2 import Template from "../components/Template";3 // Init state here45 export default function Home() {6 const [userDetails, setUserDetails] = useState(initialState);7 const [showPDF, setShowPDF] = useState(false);89 // handleSubmit function here1011 return (12 <div className={styles.container}>13 // Head tag here14 <main className={styles.main}>15 // Form component here16 {showPDF && (17 <Template18 userDetails={userDetails}19 setShowPDF={setShowPDF}20 setUserDetails={setUserDetails}21 initialState={initialState}22 />23 )}24 </main>25 </div>26 );27 }
In the above snippet, we import the Template.js
file from the components folder, used the showPDF
boolean to render it conditionally, and then passed-in the required properties.
In the browser, when a user fills the form and clicks on the Generate Resume button, the resume gets generated with the information the user provided.
Conclusion
This post explored rich Cloudinary transformation APIs and discussed building a resume generator app that collects user information and generates a PDF resume with Next.js.
Resources
Cloudinary text overlays might be a helpful resource.