DEV Community

彭勇
彭勇

Posted on

I Built a Free Text Repeater Tool for WhatsApp – Here's What I Learned About SEO and Cloudflare Pages Deployment

Why I Built This

A friend kept manually copy-pasting "sorry 🙏" over and over to send dramatic apology messages on WhatsApp. I watched him do it for about 30 seconds before thinking — this is a solved problem.

A few days later, TextRepeater was live.

It lets you type any text, set how many times to repeat it (up to 1000), pick a separator, add emoji scene modes, and apply 15 visual effects — all in the browser, no account, no backend.


The Stack

Deliberately simple:

  • Next.js 14 with output: 'export' (fully static)
  • Tailwind CSS for styling
  • Cloudflare Pages for hosting
  • GitHub Actions for CI/CD

No database. No API routes. No auth. Everything runs client-side.


Deployment: GitHub Actions + Cloudflare Pages Direct Upload

The interesting part was automating deployment. Cloudflare Pages has a GitHub App integration, but connecting it via API throws a cryptic error:

error code: 8000011 — There is an internal issue with your Cloudflare Pages Git installation.
Enter fullscreen mode Exit fullscreen mode

This happens because the GitHub App OAuth flow requires browser authorization — it can't be done purely through the API.

The workaround: use Direct Upload mode instead of GitHub integration.

Step 1 — Create the Pages project via API (no source field)

curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/pages/projects" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "text-repeater",
    "production_branch": "main"
  }'
Enter fullscreen mode Exit fullscreen mode

Step 2 — GitHub Actions workflow

name: Deploy to Cloudflare Pages

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run build

      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy out --project-name=text-repeater --branch=main
Enter fullscreen mode Exit fullscreen mode

Every push to main triggers a full build and deploy. Zero manual steps.


Next.js Config for Static Export

For Cloudflare Pages to serve routes correctly:

// next.config.mjs
const nextConfig = {
  output: 'export',
  trailingSlash: true,   // important for Cloudflare routing
  images: {
    unoptimized: true,   // required for static export
  },
}

export default nextConfig
Enter fullscreen mode Exit fullscreen mode

trailingSlash: true is the one that trips people up — without it, direct URL navigation breaks on Cloudflare Pages.


Custom Domain via Cloudflare API

After purchasing the domain, instead of clicking through the dashboard, I wired everything via API:

# 1. Bind domain to Pages project
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/pages/projects/text-repeater/domains" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"name": "textrepeater.top"}'

# 2. Add CNAME record (proxied = orange cloud enabled)
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{
    "type": "CNAME",
    "name": "textrepeater.top",
    "content": "text-repeater-9lr.pages.dev",
    "proxied": true
  }'
Enter fullscreen mode Exit fullscreen mode

SSL certificate issued automatically within ~2 minutes.


SEO for a Static Next.js Site

Launching a new domain means starting from zero authority. Here's what I set up on day one:

1. Canonical URLs (every page)

export const metadata: Metadata = {
  alternates: {
    canonical: 'https://textrepeater.top/apology-mode/',
  },
}
Enter fullscreen mode Exit fullscreen mode

Without this, search engines might treat http vs https or trailing slash variants as duplicate pages, splitting your ranking signal.

2. robots.txt

User-agent: *
Allow: /

Sitemap: https://textrepeater.top/sitemap.xml
Enter fullscreen mode Exit fullscreen mode

Obvious but easy to forget on a static site.

3. sitemap.xml with lastmod

<url>
  <loc>https://textrepeater.top/</loc>
  <lastmod>2026-03-30</lastmod>
  <changefreq>weekly</changefreq>
  <priority>1.0</priority>
</url>
Enter fullscreen mode Exit fullscreen mode

4. Open Graph + Twitter Card

openGraph: {
  type: 'website',
  siteName: 'TextRepeater',
  url: 'https://textrepeater.top',
  title: 'Text Repeater Online — Repeat Any Text Instantly',
  description: '...',
},
twitter: {
  card: 'summary',
  title: 'Text Repeater Online — Repeat Any Text Instantly',
  description: '...',
},
Enter fullscreen mode Exit fullscreen mode

5. JSON-LD Structured Data

For the homepage:

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify({
      '@context': 'https://schema.org',
      '@type': 'WebSite',
      name: 'TextRepeater',
      url: 'https://textrepeater.top',
    }),
  }}
/>
Enter fullscreen mode Exit fullscreen mode

For tool pages, use WebApplication with offers.price: '0' — this can show a free badge in rich results.


The "Scene Mode" Idea

The differentiator I'm most proud of is Scene Modes — instead of just repeating text, you pick a context (apology, birthday, anger) and the tool automatically inserts themed emoji patterns throughout your message.

Input:  "I'm really sorry"
Output: "🙏 I'm really sorry 😔 I'm really sorry 💦 I'm really sorry 🥺"
Enter fullscreen mode Exit fullscreen mode

It maps to specific long-tail keywords like "apology text generator" and "sorry message generator for WhatsApp" — much lower competition than "text repeater" alone.

Each Scene Mode is its own page with targeted metadata, giving the site multiple entry points for organic traffic.


What's Next

  • Completing the remaining Scene Modes (Heart, Anger, Birthday, Like)
  • Adding more WhatsApp-specific templates
  • Tracking which visual effects actually get used

Try It

textrepeater.top — free, no sign-up, works on mobile.

If you're building something similar (static Next.js + Cloudflare Pages), the Direct Upload + GitHub Actions approach is the smoothest path I've found. Happy to answer questions in the comments.

Top comments (0)