DeepL API Integration — Adding Translation to a Next.js App
Choosing the Right Translation API Matters More Than You'd Think
When I built a "Korean → English draft translation" button for my blog admin panel, I started with a free API. It seemed to work — until markdown special characters like ** and ## kept breaking during translation. After a few frustrating attempts, I switched to DeepL, and the problem was completely gone.
DeepL offers:
- 500,000 characters/month free (enough to translate hundreds of blog posts)
- Preserves markdown special characters without mangling them
- Translation quality for Korean↔English that free alternatives can't match
Here's the setup process I went through firsthand.
Prerequisites
- Next.js project (App Router)
- DeepL account (credit card required, free plan available)
Step 1. Create a DeepL Account and Get an API Key
- Go to deepl.com/pro#developer
- Select the DeepL API Free plan
- Enter your card information (no charge within the free limit)
- After signing up, find your key under Account → API Keys
The key looks like this:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:fx
The :fx suffix means it's a Free plan API key. Pro plan keys don't have :fx.
Important: Never put your API key directly in code. Always manage it as an environment variable.
Step 2. Set Up Environment Variables
Local development — .env.local:
DEEPL_API_KEY=your_api_key_here
Vercel deployment:
Settings → Environment Variables → add DEEPL_API_KEY
Step 3. Create a Server Route
The DeepL API key must not be exposed to the client, so calls must go through a server-side API route.
Create app/api/translate/route.ts:
import { NextRequest, NextResponse } from "next/server";
// Free plan: api-free.deepl.com / Pro plan: api.deepl.com
const DEEPL_API_URL = "https://api-free.deepl.com/v2/translate";
export async function POST(req: NextRequest) {
const apiKey = process.env.DEEPL_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: "DEEPL_API_KEY is not configured." },
{ status: 500 }
);
}
const { text, sourceLang, targetLang } = await req.json();
const res = await fetch(DEEPL_API_URL, {
method: "POST",
headers: {
Authorization: `DeepL-Auth-Key ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: [text], // passed as array (multiple texts can be translated at once)
source_lang: sourceLang, // "KO", "EN", etc.
target_lang: targetLang, // "EN", "KO", etc.
}),
});
if (!res.ok) {
const err = await res.text();
return NextResponse.json(
{ error: `DeepL API error: ${err}` },
{ status: res.status }
);
}
const data = await res.json();
const translated = data.translations?.[0]?.text ?? "";
return NextResponse.json({ translated });
}
Step 4. Call the Route from the Client
In your component, call the server route you just created — not DeepL directly.
async function translateText(text: string, direction: "ko-en" | "en-ko") {
const res = await fetch("/api/translate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text,
sourceLang: direction === "ko-en" ? "KO" : "EN",
targetLang: direction === "ko-en" ? "EN" : "KO",
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error ?? "Translation failed");
}
const data = await res.json();
return data.translated; // translated text
}
Wire it up to a button:
<button onClick={async () => {
const result = await translateText(koreanText, "ko-en");
setEnglishText(result);
}}>
Translate to English
</button>
Common Language Codes
| Language | source_lang / target_lang |
|---|---|
| Korean | KO |
| English (US) | EN-US |
| English (UK) | EN-GB |
| Japanese | JA |
| Chinese (Simplified) | ZH-HANS |
source_langsupports auto-detection and can be omitted.target_langis required.
Troubleshooting
403 Forbidden
- The API key is wrong or the environment variable isn't configured properly
- Using a Free plan key but calling
api.deepl.com(Pro URL) → change toapi-free.deepl.com
456 Quota Exceeded
- Exceeded the monthly free limit (500,000 characters)
- Check usage in your DeepL account
Empty translation result
- Make sure you're accessing
data.translations?.[0]?.text - An empty string request returns an empty result
Translation not working after Vercel deployment
- Make sure you added
DEEPL_API_KEYto Vercel environment variables and then redeployed - Adding the variable alone doesn't apply it — a redeploy is required
Summary — The Core Flow
1. Sign up for DeepL API Free → get API key
(deepl.com/pro#developer)
2. Set environment variable
.env.local → DEEPL_API_KEY=...
Vercel → add the same to Environment Variables
3. Create server route
app/api/translate/route.ts
→ uses process.env.DEEPL_API_KEY
→ calls https://api-free.deepl.com/v2/translate
4. Call the server route from the client
fetch("/api/translate", { method: "POST", body: ... })
⚠️ Never put the API key directly in client-side code
The setup itself takes under 30 minutes. With a generous free tier, it's a great fit for personal blogs or side projects that need translation features.
backtodev
A 40-something PM returns to code. Learning, failing, and growing.