Hey dev.to! My name is Maksim, and I want to share the story of building Rsale.net — a classified platform for Serbia. This is a story about technical decisions, architectural trade-offs, and the specifics of the Balkan market.
Project Status: Backend and frontend are currently under active development. The site currently displays test data to demonstrate functionality.
Why Serbia?
Serbia is an interesting market for classifieds:
- 7 million population with a growing e-commerce segment
- Dominated by outdated platforms with 2010s-era UX
- High smartphone penetration rate
- Active Russian-speaking community (relocants)
- We saw an opportunity to create a modern platform that combines products and services with a focus on multilingualism and geolocation.
Tech Stack: Why These Choices
Architecture: Microservices
We chose a microservices architecture with clear separation of responsibilities:
┌─────────────────────────────────────────────────────────┐
│ Frontend │
│ Next.js 15 + React 19 │
└─────────────────────┬───────────────────────────────────┘
│ REST API
▼
┌─────────────────────────────────────────────────────────┐
│ API Gateway │
│ ASP.NET Core │
└───┬─────────────┬─────────────┬─────────────┬───────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐
│Products│ │ Services │ │ Auth │ │Translation │
│Service │ │ Service │ │ Service │ │ Service │
│ │ │ │ │ (OIDC) │ │ (AI) │
└────────┘ └──────────┘ └──────────┘ └────────────┘
Why microservices for an MVP? The common advice is "start with a monolith". But we had reasons:
- Independent scaling — the translation service is under heavier load than others
- Different teams — frontend and backend are developed in parallel
- AI component isolation — Translation Service can be easily replaced or updated
Why ASP.NET Core?
Performance — one of the fastest web frameworks (TechEmpower benchmarks)
Type safety — C# catches errors at compile time
Ecosystem — Entity Framework, Identity, SignalR out of the box
Hosting — works great in Docker/Kubernetes
Frontend: Next.js 15 + React 19
// package.json
{
"dependencies": {
"next": "15.4.6",
"react": "19.1.0",
"next-intl": "^4.6.1", // i18n
"zustand": "^5.0.8", // State management
"framer-motion": "^12.23.24", // Animations
"@microsoft/signalr": "^9.0.6" // Real-time chat
}
}
Why Next.js 15?
- App Router — native Server Components support
- ISR (Incremental Static Regeneration) — critical for classified SEO
- next-intl — best integration for multilingualism
Frontend + Backend Integration
Frontend communicates with ASP.NET Core via REST API with retry logic:
// lib/server-api-service.ts
async function fetchWithRetry<T>(endpoint: string): Promise<T> {
const url = `${API_CONFIG.EXTERNAL_API_URL}${endpoint}`;
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(url, {
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'RSALE-Frontend/1.0'
}
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (attempt === 3) throw error;
await new Promise(r => setTimeout(r, 1000));
}
}
}
ISR Architecture: The Key to SEO
For classifieds, SEO is everything. Every listing must be indexed by Google. We use different revalidation intervals:
// lib/config.ts
export const ISR_CONFIG = {
REVALIDATE_MAIN: 300, // Homepage: 5 min
REVALIDATE_PRODUCTS: 600, // Lists: 10 min
REVALIDATE_DETAILS: 1200, // Product pages: 20 min
};
Server Components vs Client Components
We developed the "Server-First + Client Wrapper" pattern:
// app/[locale]/page.tsx — Server Component
export default async function HomePage({ params }) {
const { locale } = await params;
setRequestLocale(locale);
return (
<>
{/* SEO-critical content renders on server */}
<script type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData.website())
}}
/>
<Suspense fallback={<CategoriesSkeleton />}>
<MainPageContent /> {/* Server Component */}
</Suspense>
</>
);
}
The rule: Everything the search bot sees — server component. Interactivity (modals, forms, auth) — client components.
Three Languages: Serbian, English, Russian
Multilingualism was mandatory from day one:
// app/[locale]/products/page.tsx
export default async function ProductsPage({ params }) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations('navigation');
return (
<h1>{t('products')}</h1>
// ru: "Товары", sr: "Производи", en: "Products"
);
}
Important lesson: Serbian has two alphabets — Cyrillic and Latin. We chose Cyrillic as more traditional for the older generation.
AI Translation of Listings on the Server
One of our key features — automatic listing translation using AI. When a user adds a product or service in one language, the Translation Service automatically translates the content into the other two languages:
User writes in Russian:
"Продаю iPhone 14 Pro, отличное состояние, полный комплект"
↓ AI Translation Service ↓
Serbian: "Продајем iPhone 14 Pro, одлично стање, комплетан сет"
English: "Selling iPhone 14 Pro, excellent condition, full set"
Backend implementation (ASP.NET Core):
// TranslationService.cs
public class AITranslationService : ITranslationService
{
private readonly IAIClient _aiClient;
public async Task<TranslatedContent> TranslateAsync(
string title,
string description,
string sourceLang)
{
var targetLanguages = new[] { "ru", "sr", "en" }
.Where(l => l != sourceLang);
var translations = new Dictionary<string, LocalizedContent>();
foreach (var targetLang in targetLanguages)
{
var translated = await _aiClient.TranslateAsync(
new TranslationRequest
{
Title = title,
Description = description,
From = sourceLang,
To = targetLang
});
translations[targetLang] = translated;
}
return new TranslatedContent
{
Original = new LocalizedContent { Title = title, Description = description },
SourceLanguage = sourceLang,
Translations = translations
};
}
}
Why this matters:
- Lower barrier to entry — sellers don't think about translation
- SEO in all languages — listings are indexed in three language versions
- 3x audience — a Russian-speaking seller reaches Serbian and English-speaking buyers
Current Development Metrics
Metric Value
Build Time (frontend) ~3 sec
Bundle Size 99.7 kB
Lighthouse Performance 90+
SEO Score 9/10
Static Pages 72
What's Next?
- We're actively working on:
- Completing the backend — transitioning from test data to production
- PWA — offline mode for mobile
- Real-time chat — SignalR is already connected on both ends
- On-Demand Revalidation — webhook from ASP.NET Core to Next.js when listings change
- Improving AI translation — contextual translation based on product category
Conclusion
- Building a classified platform in 2024-2025 isn't rocket science, but it requires thoughtful technical decisions:
- ASP.NET Core for a reliable and fast backend
- Microservices for independent scaling (especially AI translation)
- Next.js 15 with ISR for SEO and performance
- AI translation to remove language barriers
- The project is still in development, but the architecture is already set up right — and that's what matters. If you have questions about our stack or architecture — drop them in the comments!
rsale.net - check out the demo

Top comments (0)