Dev.to WebDev 🛠 Dev 👁 0 📖 6 min read

How to Set Up a Voice-Enabled Blog with AI Narration

Why a Voice‑Enabled Blog? If you’ve ever wished your readers could “listen” to your posts while they’re on a commute, a workout, or just taking a break from the screen, you’re not alone. Adding AI‑generated narration t

Why a Voice‑Enabled Blog?

If you’ve ever wished your readers could “listen” to your posts while they’re on a commute, a workout, or just taking a break from the screen, you’re not alone. Adding AI‑generated narration turns a static article into a multi‑modal experience, boosts accessibility, and can even improve SEO (Google loves rich media).

In this guide we’ll walk through the whole stack:

  1. Create a TTS voice with ElevenLabs – the most natural‑sounding, customizable voice cloning platform.
  2. Hook the voice into a static site (e.g., a Jekyll or Hugo blog).
  3. Deploy the site on Bluehost, the easy, affordable way to get your voice‑enabled blog online.

By the end you’ll have a working blog where each post automatically gets a “Play” button that streams a high‑quality audio version of the article.

1. Setting Up ElevenLabs for Text‑to‑Speech

ElevenLabs offers a REST API that lets you generate speech from plain text in just a few lines of code. Sign up using this affiliate link to get a free credit bundle: ElevenLabs – AI Voice Generation.

1.1 Get Your API Key

After registration:

  1. Go to Dashboard → API Keys.
  2. Click Create new key and copy the value – you’ll need it in your code.

1.2 Choose or Clone a Voice

ElevenLabs provides several pretrained voices. If you want a unique brand voice, you can upload a few minutes of your own recordings and let the platform clone it. For this tutorial we’ll use the default “Rachel” voice (ID: EXAVITQu4vr4xnSDxMaL).

2. Building the Audio Generator

We’ll create a tiny Flask (Python) micro‑service that receives article markdown, strips the front‑matter, and returns an MP3 URL. You can also call the API directly from JavaScript, but a backend gives you better control over rate limits and secret handling.

2.1 Install Dependencies

python -m venv venv
source venv/bin/activate
pip install flask requests python-dotenv

Create a .env file:

ELEVENLABS_API_KEY=your_api_key_here

2.2 Flask App (app.py)

import os
import requests
from flask import Flask, request, jsonify
from dotenv import load_dotenv

load_dotenv()
API_KEY = os.getenv("ELEVENLABS_API_KEY")
BASE_URL = "https://api.elevenlabs.io/v1"

app = Flask(__name__)

def generate_speech(text, voice_id="EXAVITQu4vr4xnSDxMaL"):
    headers = {
        "xi-api-key": API_KEY,
        "Content-Type": "application/json"
    }
    payload = {
        "text": text,
        "voice_settings": {"stability": 0.75, "similarity_boost": 0.85}
    }
    response = requests.post(
        f"{BASE_URL}/text-to-speech/{voice_id}",
        json=payload,
        headers=headers,
        stream=True,
    )
    if response.status_code != 200:
        raise Exception(f"ElevenLabs error: {response.text}")

    # Save to a temporary file (could be S3, Cloudflare R2, etc.)
    filename = f"static/audio/{hash(text)}.mp3"
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, "wb") as f:
        for chunk in response.iter_content(chunk_size=8192):
            f.write(chunk)
    return f"/{filename}"

@app.route("/api/narrate", methods=["POST"])
def narrate():
    data = request.get_json()
    markdown = data.get("markdown", "")
    # Very naive markdown → plain text conversion
    plain = markdown.replace("**", "").replace("*", "")
    audio_url = generate_speech(plain)
    return jsonify({"audio_url": audio_url})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

What the code does

  • Accepts a JSON payload with a markdown field.
  • Strips simple markdown formatting (you can swap in a full parser later).
  • Calls the ElevenLabs TTS endpoint and streams the MP3 back to a local static/audio folder.
  • Returns the relative URL so the front‑end can embed an <audio> tag.

2.3 Test with cURL

curl -X POST http://localhost:5000/api/narrate \
     -H "Content-Type: application/json" \
     -d '{"markdown":"# Hello World\nThis is a **voice‑enabled** blog post!"}'

You should receive a JSON response like:

{
  "audio_url": "/static/audio/1234567890.mp3"
}

Open that URL in your browser – you’ll hear the narration powered by ElevenLabs.

3. Integrating the Player into Your Blog

Assuming you’re using a static site generator (Jekyll, Hugo, or even plain Markdown with a custom build step), you can inject a small JavaScript snippet that calls the /api/narrate endpoint for each post.

3.1 Front‑End Script (assets/js/narrate.js)

<script>
document.addEventListener("DOMContentLoaded", () => {
  const postContent = document.querySelector(".post-content");
  if (!postContent) return;

  const markdown = postContent.innerText; // simplistic extraction
  fetch("/api/narrate", {
    method: "POST",
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify({markdown})
  })
  .then(r => r.json())
  .then(data => {
    const audio = document.createElement("audio");
    audio.controls = true;
    audio.src = data.audio_url;
    const container = document.createElement("div");
    container.style.marginBottom = "1rem";
    container.appendChild(audio);
    postContent.prepend(container);
  })
  .catch(err => console.error("Narration error:", err));
});
</script>

Place the script at the bottom of your post layout. When a visitor lands on a page, the script:

  1. Grabs the visible text.
  2. Sends it to the Flask service.
  3. Receives the MP3 URL and injects a native HTML5 player right above the article.

That’s it—no heavy front‑end frameworks required.

4. Deploying to Bluehost

Now that the code works locally, you need a place to host both the static assets and the Flask API. Bluehost offers a one‑click WordPress/Hugo install, SSH access, and cheap shared‑hosting plans that are perfect for small AI projects. The platform’s UI makes it trivial to point a domain, enable HTTPS, and spin up a Python environment.

4.1 Spin Up a New Site

  1. Sign up via this affiliate link: Bluehost – Affordable Hosting.
  2. Choose the “Basic” plan (it includes enough CPU for a low‑traffic TTS endpoint).
  3. During setup, select “Custom PHP/Node/Python” to enable a virtual environment.

4.2 Deploy Your Code

git clone https://github.com/yourname/voice‑blog.git
cd voice‑blog
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
  • Set up a simple systemd service (Bluehost supports systemctl on most plans) to keep Flask running:
# /etc/systemd/system/voiceblog.service
[Unit]
Description=Voice‑Enabled Blog API
After=network.target

[Service]
User=youruser
WorkingDirectory=/home/youruser/voice-blog
Environment="PATH=/home/youruser/voice-blog/venv/bin"
ExecStart=/home/youruser/voice-blog/venv/bin/python app.py

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable voiceblog
sudo systemctl start voiceblog
  • Configure Apache/Nginx (Bluehost’s control panel lets you add a reverse proxy) to forward /api/ requests to http://127.0.0.1:5000.

4.3 Serve the Static Site

If you’re using Hugo:

hugo -D
rsync -avz public/ ~/public_html/

Bluehost’s built‑in file manager or FTP can also handle the upload.

That’s all the heavy lifting—your blog now lives at https://yourdomain.com with a fully functional “Play” button on every post.

5. Fine‑Tuning the Voice (Optional)

ElevenLabs lets you adjust stability (how consistent the voice sounds) and similarity boost (how closely it matches a cloned voice). Experiment with these values in the voice_settings payload to get the right tone for your brand. For podcasts, a higher stability (0.9) often feels more professional, while a conversational blog might benefit from a lower value (0.6) that adds a bit of natural variance.

If you decide to clone your own voice:

  1. Record 5‑10 short sentences (≈30 seconds total).
  2. Upload them under Voice Lab → Clone voice.
  3. Replace the voice_id in generate_speech with the new ID.

6. Scaling Considerations

  • Caching – Store generated MP3s in a CDN (Cloudflare or AWS S3) to avoid re‑synthesizing the same article on every request.
  • Rate Limits – ElevenLabs free tier allows ~200,000 characters per month. For higher traffic, upgrade the plan or batch‑process articles ahead of time.
  • Security – Keep your API key out of the client side; the Flask wrapper ensures the secret never hits the browser.

7. Wrap‑Up

You now have a complete, production‑ready pipeline:

  • ElevenLabs provides lifelike AI narration.
  • Flask turns markdown into audio on demand.
  • Bluehost offers a low‑cost, beginner‑friendly host that handles both static files and the Python backend.

Give it a spin, iterate on the voice, and watch your readership grow—people love consuming content hands‑free.

🎧 Ready to give your blog a voice?

Start generating natural‑sounding audio with ElevenLabs – AI Voice Generation and launch your site on the hassle‑free, budget‑friendly platform Bluehost – Affordable Hosting today. Happy coding!

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.