Skip to main content

Overview

The Resume Generation feature creates ATS-friendly, job-tailored resume PDFs using AI. The system analyzes the user’s original resume/profile and the target job description, then generates an HTML resume optimized for the specific role and converts it to PDF using Puppeteer.

User Workflow

Users can generate a tailored resume directly from their interview report:
  1. Navigate to an interview report page (/interview/:id)
  2. Click “Download Resume” button in the left sidebar
  3. AI generates job-specific resume in ~10-15 seconds
  4. PDF automatically downloads to browser
Interview.jsx:105-110
<button
    onClick={() => { getResumePdf(interviewId) }}
    className='button primary-button' >
    <svg>...</svg>
    Download Resume
</button>
The resume is dynamically generated each time - it’s not cached. This ensures the latest AI model improvements are always used.

API Endpoint

Generate Resume PDF: POST /api/interview/resume/pdf/:interviewReportId Authentication: Required (JWT token) Parameters:
  • interviewReportId (path parameter) - The ID of the interview report
Response:
  • Content-Type: application/pdf
  • Content-Disposition: attachment; filename=resume_{interviewReportId}.pdf
  • Body: PDF file buffer

Backend Processing Flow

1. Retrieve Interview Report Data

The controller fetches the associated interview report:
interview.controller.js:172-193
async function generateResumePdfController(req, res) {
    const { interviewReportId } = req.params

    const interviewReport = await interviewReportModel.findById(interviewReportId)

    if (!interviewReport) {
        return res.status(404).json({
            message: "Interview report not found."
        })
    }

    const { resume, jobDescription, selfDescription } = interviewReport

    const pdfBuffer = await generateResumePdf({ resume, jobDescription, selfDescription })

    res.set({
        "Content-Type": "application/pdf",
        "Content-Disposition": `attachment; filename=resume_${interviewReportId}.pdf`
    })

    res.send(pdfBuffer)
}

2. AI HTML Generation

The AI service generates structured HTML content using a Zod schema:
ai.service.js:100-102
const resumePdfSchema = z.object({
    html: z.string().describe("The HTML content of the resume which can be converted to PDF using any library like puppeteer")
})
AI Prompt:
ai.service.js:104-115
const prompt = `Generate resume for a candidate with the following details:
    Resume: ${resume}
    Self Description: ${selfDescription}
    Job Description: ${jobDescription}

    The response should be a JSON object with a single field "html" which contains the HTML content of the resume.
    The resume should be tailored for the given job description and should highlight the candidate's strengths and relevant experience.
    The HTML content should be well-formatted and structured, making it easy to read and visually appealing.
    The content of resume should not sound like it's generated by AI and should be as close as possible to a real human-written resume.
    You can highlight the content using some colors or different font styles but the overall design should be simple and professional.
    The content should be ATS friendly, i.e. it should be easily parsable by ATS systems without losing important information.
    The resume should not be so lengthy, it should ideally be 1-2 pages long when converted to PDF.
`
AI API Call:
ai.service.js:117-124
const response = await ai.models.generateContent({
    model: "gemini-3-flash-preview",
    contents: prompt,
    config: {
        responseMimeType: "application/json",
        responseSchema: zodToJsonSchema(resumePdfSchema),
    }
})
The AI is specifically instructed to create ATS-friendly content that doesn’t sound AI-generated. It tailors the resume to highlight relevant experience for the target job description.

3. HTML to PDF Conversion

Puppeteer converts the AI-generated HTML to a professionally formatted PDF:
ai.service.js:79-96
async function generatePdfFromHtml(htmlContent) {
    const browser = await puppeteer.launch()
    const page = await browser.newPage();
    await page.setContent(htmlContent, { waitUntil: "networkidle0" })

    const pdfBuffer = await page.pdf({
        format: "A4",
        margin: {
            top: "20mm",
            bottom: "20mm",
            left: "15mm",
            right: "15mm"
        }
    })

    await browser.close()

    return pdfBuffer
}
PDF Configuration:
  • Format: A4 (standard resume size)
  • Margins: 20mm top/bottom, 15mm left/right
  • Wait Strategy: networkidle0 ensures all resources are loaded

4. Complete Generation Flow

The full service combines AI generation and PDF conversion:
ai.service.js:98-133
async function generateResumePdf({ resume, selfDescription, jobDescription }) {

    const resumePdfSchema = z.object({
        html: z.string().describe("The HTML content of the resume which can be converted to PDF using any library like puppeteer")
    })

    const prompt = `Generate resume for a candidate with the following details:
                        Resume: ${resume}
                        Self Description: ${selfDescription}
                        Job Description: ${jobDescription}
                        ...
                    `

    const response = await ai.models.generateContent({
        model: "gemini-3-flash-preview",
        contents: prompt,
        config: {
            responseMimeType: "application/json",
            responseSchema: zodToJsonSchema(resumePdfSchema),
        }
    })

    const jsonContent = JSON.parse(response.text)

    const pdfBuffer = await generatePdfFromHtml(jsonContent.html)

    return pdfBuffer
}

Resume Tailoring Strategy

The AI optimizes resumes using several strategies:

Keyword Optimization

Incorporates relevant keywords from the job description for ATS compatibility

Experience Highlighting

Emphasizes relevant experience and de-emphasizes unrelated roles

Skill Matching

Prominently displays skills that match job requirements

Format Optimization

Creates clean, professional formatting that renders well in PDF

Design Guidelines

The AI follows specific design principles:

Professional Appearance

  • Simple, clean layouts
  • Appropriate use of color for highlights
  • Consistent font styles and sizes
  • Proper spacing and hierarchy

ATS Compatibility

  • Text-based content (no images or graphics that can’t be parsed)
  • Standard section headings
  • Simple table structures if used
  • No complex formatting that breaks parsing

Length Optimization

  • Target: 1-2 pages when converted to PDF
  • Concise bullet points
  • Focus on quality over quantity
  • Relevant information prioritized
The AI analyzes:
  • User’s original resume content
  • Self-description if provided
  • Target job description requirements
  • Skills and experience alignment
Then tailors:
  • Summary/objective statement
  • Work experience descriptions
  • Skills section order and content
  • Education and certifications relevance

File Download Flow

The frontend handles the PDF download:
interview.api.js (typical pattern)
export const getResumePdf = async (interviewReportId) => {
    const response = await fetch(
        `/api/interview/resume/pdf/${interviewReportId}`,
        {
            method: 'POST',
            credentials: 'include', // Include auth cookie
        }
    )
    
    const blob = await response.blob()
    const url = window.URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `resume_${interviewReportId}.pdf`
    a.click()
    window.URL.revokeObjectURL(url)
}
Download Steps:
  1. POST request to PDF generation endpoint
  2. Server generates PDF and returns binary data
  3. Frontend creates blob URL from response
  4. Programmatic anchor click triggers download
  5. Cleanup blob URL after download

Performance Considerations

Generation Time

~10-15 seconds average (AI generation + PDF conversion)

File Size

Typically 50-200 KB depending on content length

Browser Compatibility

Works in all modern browsers with blob download support

Concurrent Requests

Puppeteer handles concurrent generations with separate browser instances

Error Handling

The system handles various error scenarios: Interview Report Not Found:
{
  "message": "Interview report not found."
}
Status: 404 AI Generation Failure: If Gemini AI fails, the error is caught and a 502 is returned (similar to interview report generation). PDF Conversion Failure: If Puppeteer fails to launch or convert, the server will throw an error. Production systems should catch this and return appropriate error responses.

Best Practices

For Users:
  • Upload detailed resumes for best tailoring results
  • Include self-description to provide additional context
  • Review and customize the generated PDF before submitting to employers
  • Generate fresh PDFs for each unique job application
Technical Notes:
  • PDFs are generated on-demand (not pre-generated/cached)
  • Each generation gets the latest AI model improvements
  • Puppeteer runs in headless mode for server efficiency
  • Browser instances are properly closed after each generation

Security Considerations

User Isolation:
  • Resumes are only generated for interview reports owned by the authenticated user
  • No access to other users’ data
Resource Management:
  • Puppeteer browser instances are properly closed
  • Prevents memory leaks from abandoned browser processes
Content Validation:
  • AI-generated HTML is from trusted source (Gemini API)
  • No user-provided HTML is rendered (prevents XSS)

Future Enhancements

Potential improvements to the resume generation system:
  • Multiple resume template styles
  • Custom branding/color schemes
  • Resume editing before PDF generation
  • Version history and comparison
  • A/B testing different resume variations
  • Integration with LinkedIn profile data
  • Export to DOCX format option

Build docs developers (and LLMs) love