Реализация White-Label версии веб-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация White-Label версии веб-приложения
Сложная
~2-4 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

White-Label веб-приложение

White-label позволяет клиентам продавать ваш продукт под своим брендом: свой домен, логотип, цвета, email-шаблоны. Пользователи клиента не знают о вашей платформе.

Архитектура White-Label

Два подхода к изоляции:

По субдоменуacme.yourplatform.com или app.acmecorp.com (custom domain)

По записям DNS — клиент добавляет CNAME: app.acmecorp.com → yourplatform.com

// middleware.ts: определяем тенанта по домену
export async function middleware(request: NextRequest) {
  const hostname = request.headers.get('host')!;

  // Убираем порт для localhost
  const domain = hostname.replace(':3000', '');

  // Загружаем tenant по домену
  const tenant = await getTenantByDomain(domain);

  if (!tenant) {
    return NextResponse.rewrite(new URL('/404', request.url));
  }

  // Пробрасываем tenant в заголовках
  const response = NextResponse.next();
  response.headers.set('x-tenant-id', tenant.id);
  response.headers.set('x-tenant-slug', tenant.slug);

  return response;
}

Custom Domain: верификация

// При добавлении custom domain клиентом
// 1. Клиент добавляет CNAME в DNS
// 2. Мы верифицируем через DNS lookup
// 3. Выпускаем SSL через Let's Encrypt или Cloudflare

export async function verifyCustomDomain(tenantId: string, domain: string) {
  const dns = await import('dns/promises');

  // Проверяем CNAME запись
  let cnameTarget: string;
  try {
    const records = await dns.resolveCname(domain);
    cnameTarget = records[0];
  } catch {
    return { verified: false, error: 'CNAME not found' };
  }

  const expectedCname = `${process.env.PLATFORM_DOMAIN}`;
  if (cnameTarget !== expectedCname) {
    return {
      verified: false,
      error: `CNAME must point to ${expectedCname}, found: ${cnameTarget}`
    };
  }

  // Сохраняем домен
  await db.tenant.update({
    where: { id: tenantId },
    data: {
      customDomain: domain,
      customDomainVerifiedAt: new Date(),
    }
  });

  // Cloudflare: добавляем custom hostname
  await addCloudflareCustomHostname(domain, tenantId);

  return { verified: true };
}

// Cloudflare Custom Hostnames API
async function addCloudflareCustomHostname(domain: string, tenantId: string) {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${process.env.CF_ZONE_ID}/custom_hostnames`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.CF_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        hostname: domain,
        ssl: { method: 'http', type: 'dv' },
        custom_metadata: { tenant_id: tenantId },
      }),
    }
  );
  return response.json();
}

Брендинг: конфигурация тенанта

model TenantBranding {
  id              String  @id @default(cuid())
  tenantId        String  @unique
  logoUrl         String?
  faviconUrl      String?
  appName         String
  primaryColor    String  @default("#6366f1")
  secondaryColor  String  @default("#f1f5f9")
  fontFamily      String  @default("Inter")
  supportEmail    String?
  supportUrl      String?
  privacyUrl      String?
  termsUrl        String?
  footerText      String?
  hideWatermark   Boolean @default(false)  // белая метка
  customCss       String? @db.Text         // произвольный CSS
  emailFromName   String?
  emailFromAddress String?
  emailLogoUrl    String?
}
// CSS переменные из настроек тенанта
export function TenantStylesheet({ branding }: { branding: TenantBranding }) {
  const css = `
    :root {
      --color-primary: ${branding.primaryColor};
      --color-secondary: ${branding.secondaryColor};
      --font-family: '${branding.fontFamily}', sans-serif;
    }
    ${branding.customCss ?? ''}
  `;

  return <style dangerouslySetInnerHTML={{ __html: css }} />;
}
// app/layout.tsx: динамическая метаинформация
import { headers } from 'next/headers';
import type { Metadata } from 'next';

export async function generateMetadata(): Promise<Metadata> {
  const tenantId = headers().get('x-tenant-id');
  const branding = await db.tenantBranding.findUnique({
    where: { tenantId: tenantId! }
  });

  return {
    title: branding?.appName ?? 'App',
    icons: { icon: branding?.faviconUrl ?? '/favicon.ico' },
  };
}

export default async function RootLayout({ children }) {
  const tenantId = headers().get('x-tenant-id');
  const branding = await db.tenantBranding.findUnique({
    where: { tenantId: tenantId! }
  });

  return (
    <html>
      <head>
        {branding?.fontFamily !== 'Inter' && (
          <link
            href={`https://fonts.googleapis.com/css2?family=${branding.fontFamily}:wght@400;500;600&display=swap`}
            rel="stylesheet"
          />
        )}
      </head>
      <body>
        <TenantStylesheet branding={branding!} />
        {children}
        {!branding?.hideWatermark && <PlatformWatermark />}
      </body>
    </html>
  );
}

Email-шаблоны с брендингом

// Все исходящие письма используют брендинг тенанта
export async function sendBrandedEmail(
  tenantId: string,
  to: string,
  template: string,
  variables: Record<string, string>
) {
  const branding = await db.tenantBranding.findUnique({
    where: { tenantId }
  });

  await resend.emails.send({
    from: branding?.emailFromAddress
      ? `${branding.emailFromName} <${branding.emailFromAddress}>`
      : `[email protected]`,
    to,
    subject: variables.subject,
    react: EmailTemplate({
      ...variables,
      logoUrl: branding?.emailLogoUrl,
      brandColor: branding?.primaryColor ?? '#6366f1',
      appName: branding?.appName ?? 'App',
      footerText: branding?.footerText,
    }),
  });
}

Разработка white-label системы с custom domains, брендингом и email-шаблонами — 5–10 рабочих дней.