HTML to Image in Vue
Generate share images from a Vue or Nuxt app, with the Composition API.
Best for: Vue 3 apps and Nuxt server routes. The Composition API hook below works in both contexts. Always proxy through your server so the API key stays private.
Patterns covering Composition API with onMounted, plus a Nuxt server route example for templates.
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"
}
Bind data.url straight to an <img :src> - no blob or object URL handling required.
Basic Component Example
Here’s a Vue component that generates images from HTML:
<template>
<div>
<button
@click="generateImage"
:disabled="loading"
>
{{ loading ? 'Generating...' : 'Generate Image' }}
</button>
<div v-if="error" class="error">
Error: {{ error }}
</div>
<div v-if="imageUrl">
<img
:src="imageUrl"
alt="Generated content"
class="generated-image"
/>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const imageUrl = ref(null);
const loading = ref(false);
const error = ref(null);
const generateImage = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': import.meta.env.VITE_HTML2IMG_API_KEY
},
body: JSON.stringify({
html: `
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Vue!</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');
}
imageUrl.value = data.url;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.error {
color: red;
margin: 1rem 0;
}
.generated-image {
max-width: 100%;
margin-top: 1rem;
}
</style>
Screenshot Component
Here’s a component for taking screenshots:
<template>
<div>
<div class="input-group">
<input
v-model="url"
type="text"
placeholder="Enter URL"
class="url-input"
/>
</div>
<button
@click="takeScreenshot"
:disabled="loading"
class="screenshot-button"
>
{{ loading ? 'Taking Screenshot...' : 'Take Screenshot' }}
</button>
<div v-if="error" class="error">
Error: {{ error }}
</div>
<div v-if="imageUrl" class="image-container">
<img
:src="imageUrl"
alt="Screenshot"
class="screenshot-image"
/>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const url = ref('https://example.com');
const imageUrl = ref(null);
const loading = ref(false);
const error = ref(null);
const takeScreenshot = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch('https://app.html2img.com/api/screenshot', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': import.meta.env.VITE_HTML2IMG_API_KEY
},
body: JSON.stringify({
url: url.value,
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');
}
imageUrl.value = data.url;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.input-group {
margin-bottom: 1rem;
}
.url-input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
.screenshot-button {
margin-bottom: 1rem;
}
.error {
color: red;
margin: 1rem 0;
}
.image-container {
margin-top: 1rem;
}
.screenshot-image {
max-width: 100%;
border: 1px solid #eee;
border-radius: 4px;
}
</style>
Composable Example
Here’s a composable function to reuse the image generation logic:
// useHtml2Img.js
import { ref } from 'vue';
export function useHtml2Img() {
const loading = ref(false);
const error = ref(null);
const generateImage = async (html, options = {}) => {
loading.value = true;
error.value = null;
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': import.meta.env.VITE_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 (err) {
error.value = err.message;
throw err;
} finally {
loading.value = false;
}
};
return {
generateImage,
loading,
error
};
}
// Usage Example
<template>
<div>
<button
@click="handleGenerate"
:disabled="loading"
>
Generate Image
</button>
<div v-if="error" class="error">
Error: {{ error }}
</div>
<div v-if="imageUrl">
<img :src="imageUrl" alt="Generated content" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useHtml2Img } from './useHtml2Img';
const { generateImage, loading, error } = useHtml2Img();
const imageUrl = ref(null);
const handleGenerate = async () => {
try {
imageUrl.value = await generateImage(`
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Composable!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`);
} catch (err) {
console.error('Failed to generate image:', err);
}
};
</script>
Environment Setup
Create a .env file in your project root:
VITE_HTML2IMG_API_KEY=your-api-key
Access the environment variable in your components:
const apiKey = import.meta.env.VITE_HTML2IMG_API_KEY;
Never expose your API key in client-side code. Use environment variables and consider proxying requests through your backend.
These examples use the Composition API with <script setup>, the recommended approach for Vue 3. Adapt to the Options API for older Vue 2 codebases.
Common patterns
Composition API with onMounted
<script setup>
import { ref, onMounted } from 'vue';
const props = defineProps({ html: String });
const url = ref(null);
const error = ref(null);
onMounted(async () => {
try {
const response = await fetch('/api/share-image', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: props.html }),
});
if (!response.ok) throw new Error(`Status ${response.status}`);
const data = await response.json();
url.value = data.url;
} catch (e) {
error.value = e;
}
});
</script>
<template>
<img v-if="url" :src="url" alt="Generated share image" />
<p v-else-if="error">Failed to render</p>
<p v-else>Rendering...</p>
</template>
Nuxt server route
// server/api/render-invoice.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
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: {
invoice_number: body.number,
business_name: 'Coastline Coffee Co',
client_name: body.client,
items: body.items,
total: body.total,
},
});
return { url: response.url };
});
Templates in Vue
Use a named template when your data is structured. Here is the Invoice Image template, called from a Nuxt server route:
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: 'INV-2026-0042',
business_name: 'Coastline Coffee Co',
client_name: 'Riverside Bakery',
items: [{ description: 'Wholesale beans', quantity: '50', unit_price: '$15.00', amount: '$750.00' }],
total: '$750.00',
}),
});
const { url } = await response.json();
The returned url is hosted on i.html2img.com.
Related guides and articles
- Browse all templates
- Open Graph Image template
- Code Screenshot template
- Articles index
- Getting started with the API
- Pricing
Common templates for Vue developers
Vue apps most often reach for these named templates: