← cd ..
DeepLTranslation APINext.jsAPI Integration

DeepL API Integration — Adding Translation to a Next.js App

April 18, 20261 min read

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

  1. Go to deepl.com/pro#developer
  2. Select the DeepL API Free plan
  3. Enter your card information (no charge within the free limit)
  4. 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

Languagesource_lang / target_lang
KoreanKO
English (US)EN-US
English (UK)EN-GB
JapaneseJA
Chinese (Simplified)ZH-HANS

source_lang supports auto-detection and can be omitted. target_lang is 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 to api-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_KEY to 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.

PM

backtodev

A 40-something PM returns to code. Learning, failing, and growing.

DeepL API Integration — Adding Translation to a Next.js App | backtodev