HTML to Image in React
Generate share images from a React app, server-side or on demand.
Best for: React apps with server actions or API routes that proxy requests, plus client-side flows where the user generates an image and copies the URL. Always proxy through your server so the API key stays private.
Patterns covering server-side rendering inside Next.js or Remix actions, plus a client-side useEffect plus useCallback hook with cleanup.
API Response Format
Every successful request returns a JSON body with a url pointing to the generated image:
{
"success": true,
"credits_remaining": 95,
"id": "abc123",
"url": "https://i.html2img.com/abc123.png"
}
Use data.url directly as the src of an <img> - no blob or object URL handling required.
Basic Component Example
Here’s a React component that generates images from HTML:
import { useState } from 'react';
function ImageGenerator() {
const [imageUrl, setImageUrl] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const generateImage = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.REACT_APP_HTML2IMG_API_KEY
},
body: JSON.stringify({
html: `
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from React!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`,
width: 800,
height: 600
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to generate image');
}
setImageUrl(data.url);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
return (
<div>
<button
onClick={generateImage}
disabled={loading}
>
{loading ? 'Generating...' : 'Generate Image'}
</button>
{error && (
<div style={{ color: 'red' }}>
Error: {error}
</div>
)}
{imageUrl && (
<div>
<img
src={imageUrl}
alt="Generated content"
style={{ maxWidth: '100%' }}
/>
</div>
)}
</div>
);
}
export default ImageGenerator;
Screenshot Component
Here’s a component for taking screenshots:
import { useState } from 'react';
function ScreenshotGenerator() {
const [url, setUrl] = useState('https://example.com');
const [imageUrl, setImageUrl] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const takeScreenshot = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://app.html2img.com/api/screenshot', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.REACT_APP_HTML2IMG_API_KEY
},
body: JSON.stringify({
url,
width: 1200,
height: 800,
dpi: 2,
fullpage: true
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to take screenshot');
}
setImageUrl(data.url);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
return (
<div>
<div>
<input
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="Enter URL"
style={{ width: '100%', marginBottom: '1rem' }}
/>
</div>
<button
onClick={takeScreenshot}
disabled={loading}
>
{loading ? 'Taking Screenshot...' : 'Take Screenshot'}
</button>
{error && (
<div style={{ color: 'red', margin: '1rem 0' }}>
Error: {error}
</div>
)}
{imageUrl && (
<div style={{ marginTop: '1rem' }}>
<img
src={imageUrl}
alt="Screenshot"
style={{ maxWidth: '100%' }}
/>
</div>
)}
</div>
);
}
export default ScreenshotGenerator;
Custom Hook Example
Here’s a custom hook to reuse the image generation logic:
import { useState, useCallback } from 'react';
function useHtml2Img() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const generateImage = useCallback(async (html, options = {}) => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.REACT_APP_HTML2IMG_API_KEY
},
body: JSON.stringify({
html,
width: options.width || 800,
height: options.height || 600,
...options
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to generate image');
}
return data.url;
} catch (error) {
setError(error.message);
throw error;
} finally {
setLoading(false);
}
}, []);
return {
generateImage,
loading,
error
};
}
// Usage Example
function MyComponent() {
const { generateImage, loading, error } = useHtml2Img();
const [imageUrl, setImageUrl] = useState(null);
const handleGenerate = async () => {
try {
const url = await generateImage(`
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Custom Hook!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`);
setImageUrl(url);
} catch (error) {
console.error('Failed to generate image:', error);
}
};
return (
<div>
<button onClick={handleGenerate} disabled={loading}>
Generate Image
</button>
{error && <div style={{ color: 'red' }}>Error: {error}</div>}
{imageUrl && <img src={imageUrl} alt="Generated content" />}
</div>
);
}
Environment Setup
Make sure to set up your environment variables:
REACT_APP_HTML2IMG_API_KEY=your-api-key
For Vite-based React projects, use:
VITE_HTML2IMG_API_KEY=your-api-key
And access it with:
const apiKey = import.meta.env.VITE_HTML2IMG_API_KEY;
Never expose your API key in client-side code. Proxy requests through your backend, including Next.js API routes or server actions.
Common patterns
useEffect plus useCallback with cleanup
import { useCallback, useEffect, useState } from 'react';
export function useShareImage(html, options = {}) {
const [url, setUrl] = useState(null);
const [error, setError] = useState(null);
const render = useCallback(async (signal) => {
try {
// POST to your own API route that holds the API key
const response = await fetch('/api/share-image', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html, ...options }),
signal,
});
if (!response.ok) throw new Error(`Status ${response.status}`);
const data = await response.json();
setUrl(data.url);
} catch (err) {
if (err.name !== 'AbortError') setError(err);
}
}, [html, options]);
useEffect(() => {
const controller = new AbortController();
render(controller.signal);
return () => controller.abort();
}, [render]);
return { url, error };
}
The AbortController cancels the in-flight request when inputs change or the component unmounts.
Templates in React
Use a named template when your data is structured. Here is the Invoice Image template, called from a Next.js Route Handler:
// app/api/render-invoice/route.ts
export async function POST(request: Request) {
const body = await request.json();
const response = await fetch('https://app.html2img.com/api/v1/templates/invoice-image', {
method: 'POST',
headers: { 'X-API-Key': process.env.HTML2IMG_API_KEY!, 'Content-Type': 'application/json' },
body: JSON.stringify({
invoice_number: body.number,
business_name: 'Coastline Coffee Co',
client_name: body.client,
items: body.items,
total: body.total,
}),
});
const { url } = await response.json();
return Response.json({ url });
}
The returned url is hosted on i.html2img.com.