How I build and manage multilingual blog & pSEO websites
Here’s how I build a blog system that supports multiple languages, scales to hundreds of SEO pages, and doesn’t require a CMS or markdown. Just clean the folder structure, static generation, and JSX. I use Cursor to manage these, so CMS is not needed.
1. Blog = Folder + Component
Each blog post is just a page.tsx file inside a language-specific folder.
Example:
src/app/en/blog/my-post-slug/page.tsx
src/app/ru/blog/my-post-slug/page.tsx
The folder name is the slug. Inside, I write the post as a JSX component. Everything - text, images, metadata - is defined there. No external CMS. No markdown. I can style, link, or structure it however I want using React.
Each post includes:
- Metadata (title, description, OpenGraph, etc.)
- Header, footer, main article
- Cover image from /public/images/
- Related articles section
- Language-specific content
2. How I add a new blog post
Here’s my actual workflow (you can also prompt this on Cursor):
mkdir -p src/app/en/blog/your-post-slug
mkdir -p src/app/ru/blog/your-post-slug
cp template.tsx src/app/en/blog/your-post-slug/page.tsx
cp template.tsx src/app/ru/blog/your-post-slug/page.tsx
Then I update:
- SEO metadata block
- Text content
- Internal links
- Image filename in /public/images/
- Translation in the Russian version
Each language is a full component, so I can tweak the writing style or structure per locale if needed.
3. Programmatic SEO pages (pSEO)
For static, SEO-optimized pages that follow a guide structure (e.g. how to buy X, where to buy Y), I don’t write them manually.
Instead:
- I define brand and locale combinations
- Each combination uses a deterministic content variation
- Variations live in src/data/content/*.ts
- Pages are statically generated using generateStaticParams()
At build time, every brand+locale gets a unique page, with a consistent structure and unique metadata. Nothing is duplicated.
Example route:
src/app/[locale]/how-to-buy/[brand]/page.tsx
The page pulls:
- Brand name
- Localized content variation
- Translations
- CTA component for that brand
4. Translation system
All translations are code-based. I use next-intl. Locale is part of the URL (/en/, /ru/). The translations live in src/messages/ as JSON files.
No runtime loading. Everything’s static at build.
I structure translations by section:
{
"blog": {
"relatedArticles": "More Articles to Read",
"readTime": "min read"
},
"cta": {
"title": "Buy and Sell {brand}s Instantly"
}
}
5. Content guidelines I follow
- One layout for all posts
- All images go in public/images/, never inline base64
- SEO titles: clear, keyword-rich, not clickbait
- All posts use H2 and H3 headers properly
- Internal links always updated (manually in moreArticles)
- Alt text required for all images
6. Why I don't use markdown or CMS
- JSX gives full control
- Easier internal linking and dynamic content blocks
- All content in Git = reviewable, revertable, portable
- No UI limitations or plugin dependency
- I never need to “integrate” anything - it’s already there
- I just prompt it all the way on Cursor
7. Deployment & maintenance
- Blog content is part of the repo
- Every edit is a pull request
- Build runs at deploy, no live editing
- I can statically render 1,000+ pages without a performance hit
This setup doesn’t just scale. It stays fast, clean, and predictable. I don’t have to chase broken CMS fields, sync translation tools, or debug UI editors. It's just folders, files, and a repeatable structure, which Cursor can manage with this setup.
Prompting guide for Cursor
When writing or editing blog posts or SEO pages using Cursor, here’s how to get the most out of prompting without wasting time or breaking structure. This makes sure you don't have to even think about that CMS you don't need.
1. For writing a new blog post
Prompt:
Write a 900-word SEO-optimized blog post titled "How to Buy [Brand] in [Country]" for our blog. The tone should be practical, clear, and structured. No fluff. Use H2s and H3s. Include 5 sections: intro, step-by-step guide, tips, payment methods, and FAQ. Add keywords naturally. Use short paragraphs.
Use cases:
- Generating pSEO content variations
- Speeding up original blog writing
- Getting draft structure instantly
Tip: Include brand and country names in your prompt to anchor the SEO context.
2. For translating posts to other languages
Prompt:
Translate this blog post into Russian using a fluent, natural tone. Keep headings, structure, and formatting intact. Localize examples and currency. Use src/messages/
conventions where relevant.
Use cases:
- Copy-pasting JSX content into Cursor for translation
- Localizing call-to-actions and metadata
Tip: Translate in chunks if the post is long. Cursor handles structure better that way.
3. For editing existing content
Prompt:
Rewrite this paragraph to make it more concise and practical. Remove filler, avoid repetition, and keep the tone direct.
Or:
Improve this section by adding a real-world example. Keep the writing clean and focused.
Use cases:
- Tightening intros or CTAs
- Rewriting weak sections
- Injecting examples or improving SEO
4. For metadata generation
Prompt:
Generate SEO metadata for a blog post titled "Where to Buy [Brand] in [Country]". Include:
- SEO title (max 60 characters)
- Meta description (max 160 characters)
- OpenGraph title/description
- Twitter card title/description
Avoid superlatives and vague phrasing.
Use cases:
- Automating metadata blocks in page.tsx
- Ensuring unique titles and descriptions per post
5. For generating pSEO content variation
Prompt:
Create 3 unique variations of a "how to buy [Brand]" guide for pSEO. Each version should have:
- A different intro angle
- Rewritten steps using different phrasing
- Slightly varied tips and FAQs
All versions must follow the same structure and length.
Use cases:
- Filling out howToBuyVariations.ts or similar
- Avoiding duplicate content
- Scaling without losing quality
Style & voice reminders
When prompting Cursor, keep these rules in your instructions:
- Avoid marketing fluff
- Short paragraphs, clear structure
- Use plain language
- Stick to the heading hierarchy
- Avoid filler words and vague phrasing
- Always localize currency, examples, and formatting
Bonus: Prompt template for internal linking
Prompt:
Given this blog post content, suggest 3 relevant internal articles from our blog based on topic overlap. Return slug + suggested link text.
Use this to update moreArticles blocks consistently.
You can paste these prompts directly into Cursor’s chat or use them in multi-step tasks. They’re built to match how the actual system works: no CMS and no guesswork.
Did this help?
If you found this useful and want more practical content like this, let me know.
Tried something similar in your own setup? Share it with me, I’m always interested in how others approach this!