DEV Community

Maksim Baranov
Maksim Baranov

Posted on

From Idea to MVP: Building a Classified Platform in Serbia

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)     │
└────────┘  └──────────┘  └──────────┘  └────────────┘
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

Why Next.js 15?

  1. App Router — native Server Components support
  2. ISR (Incremental Static Regeneration) — critical for classified SEO
  3. 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));
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
} 
Enter fullscreen mode Exit fullscreen mode

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"
  );
}
Enter fullscreen mode Exit fullscreen mode

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
        };
    }
}

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)