Pull to refresh
Аурига
Аурига — это люди

Next.js. Технология современной веб-разработки

Level of difficultyMedium
Reading time16 min
Views22K

Современная веб-разработка требует эффективных инструментов, способных справляться с высокими стандартами производительности и пользовательского опыта. В этой статье мы рассмотрим Next.js, мощный JavaScript-фреймворк и новаторское решение для создания современных веб-приложений на основе React, созданный компанией Vercel. Узнаем, как он помогает разработчикам создавать высокопроизводительные, масштабируемые и SEO-дружественные веб-приложения. Мы также глубоко погрузимся в его функциональность, рассмотрим особенности, такие как серверный рендеринг и генерация статических сайтов, и предоставим примеры использования. Давайте разберем, как Next.js становится ключевым инструментом в современной веб-разработке, обеспечивая идеальный баланс между разнообразием функций и оптимальной производительностью.

Ключевые особенности Next.js

Next.js обладает множеством функций, которые делают его привлекательным для разработчиков. Являясь библиотекой для создания пользовательских интерфейсов, Next.js также может считаться бэкенд инструментом благодаря наличию Node.js и способности аутсорсить часть выполняемой логики на сторону сервера. Давайте рассмотрим основные возможности Next.js.

1. Рендеринг на стороне сервера (SSR) и генерация статических сайтов (SSG)

Благодаря инициализации серверной прослойки на Node.js, Next.js отлично справляется с предварительным рендерингом страниц на стороне сервера, что позволяет ускорить загрузку и улучшить SEO за счет передачи HTML-контента непосредственно в браузер. Эта техника позволяет усовершенствовать пользовательский опыт и видимость в поисковых системах.

2. Автоматическое разделение кода

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

3. Встроенная поддержка CSS

Next.js легко интегрируется с различными библиотеками CSS-in-JS, что упрощает стилизацию и избавляет от необходимости использования дополнительных настроек и инструментов.

4. Горячая замена модулей (HMR)

HMR позволяет разработчикам видеть изменения в реальном времени, не требуя обновления страницы, что делает процесс разработки более эффективным и приятным.

5. Роутинг на основании файловой системы

Создание роутов в приложении Next.js не требует особых усилий. Структура папок и файлов определяет пути доступа клиентской части к компонентам приложения. Также Next.js поддерживает ключевые слова в названиях файловой системы для разделения поведения и контекста сервера и клиента. В частности, компоненты внутри папки, названной  "api", будут считаться API эндпоинтами, а не компонентами, и не будут включены в клиентскую часть приложения автоматически.  Эта возможность также облегчает интеграцию функциональности бэкенда с фронтендом, обеспечивая динамическую работу с данными.

Методики структурирования: Page роутер и App роутер

Изначально Next.js поддерживал так называемую Page структуру построения приложения, попутно работая над более интуитивно простой App cтруктурой. App роутер официально вышел из беты в марте текущего года. Давайте рассмотрим более подробно основные принципы каждой из структур.

Page роутер

Является стабильной и проверенной временем парадигмой Next.js

1. Файловая структура роутинга

Как и было сказано ранее, Page структура создает роутинг по файловой системе приложения. Точкой отсчета является папка со специальным названием pages, внутри которой каждый .jsx или .tsx файл в каталоге pages представляет собой маршрут в навигации приложения. Например, файл с именем about.tsx в папке pages генерирует маршрут /about в вашем приложении. Таким же образом можно создавать внутри pages другие папки по необходимости для группирования компонентов по их роли и направлению – файловый путь pages/help/contact-us.tsx предоставит доступ к компоненту в браузере по пути /help/contact-us. Такой подход к организации роутов повышает производительность труда разработчиков, ведь, по сравнению с типичной React-разработкой, пропадает необходимость вручную создавать и прописывать роутинг в компонентах, что к тому же и улучшает читабельность кода.

2. Динамический роутинг

Next.js обеспечивает динамическую маршрутизацию за счет использования скобок [] в имени файла. Например, файл с именем [id].tsx в папке user может соответствовать таким роутам, как /user/1, /user/2 и т.д. Динамические маршруты позволяют создавать адаптируемые и параметризованные веб-адреса, что предоставляет пользователям представление индивидуального контента.

Полную систему роутинга можно описать следующей схемой:

Page роутер
Page роутер

3. Зарезервированные имена

Для упрощения разработки приложения, помимо роутинга относительно структуры файлов, Page роутер работает с компонентами с зарезервированными именами для автоматической поддержки ряда сценариев. Основным таким компонентом является _app.jsx/tsx рутовый компонент, находящийся напрямую в папке pages. Как и в стандартных React-приложениях, его роль – предоставить инициализацию общих лэйаутов (UI-элементы такие как, например, хэдер или футер вебсайта) и любой глобально используемой логики, такой как React-контексты или управление глобальным состоянием. Другое повсеместно используемое зарезервированное имя – index. Компоненты с этим именем выводятся приложением на базовым роуты, т. е. pages/index.tsx доступен по адресу /, точно так же index-компонент внутри каждой подпапки pages будет обрабатываться по базовому роуту соответствующего адреса.

Еще несколько зарезервированных имен (файлы обязаны напрямую находиться в pages):

  •   _document.jsx/tsx – редко используемый компонент, предназначен для перезаписывания стандартной HTML структуры Next.js документа;

  • 404.jsx/tsx – компонент для кастомизации 404 ошибки Next.js сервера (например, попытка ввести несуществующий роут в url). При отсутствии кастомного компонента, Next.js статически сгенерирует стандартный во время билда приложения;

  • 500.jsx/tsx – аналогично для ошибки 500;

  • _error.jsx/tsx  – компонент для кастомизации реагирования на серверные ошибки, заменяющий стандартные 404/500 компоненты.

Типичная стуктура Page Router
Типичная стуктура Page Router

4. Автоматическое разделение кода

Next.js выполняет автоматическое разбиение кода на части, загружая только те фрагменты JavaScript, которые необходимы для текущей страницы. Это позволяет сократить время загрузки страницы и повысить общую производительность приложения. Разделение кода гарантирует, что пользователи получат оптимизированные и легкие блоки, адаптированные к их конкретным взаимодействиям.

5. Получение данных

В Page роутере реализованы такие методы получения данных, как getStaticProps и getServerSideProps. Эти методы позволяют получать данные во время рендеринга на стороне сервера или во время сборки, что позволяет предварительно рендерить страницы с динамическим содержимым. Эта возможность гарантирует, что веб-приложение будет предоставлять пользователям актуальные данные без ущерба для производительности. Давайте рассмотрим их подробней. 

getStaticProps – метод, указывающий Next-компоненту выполнить внутреннюю логику и потом передать полученные данные (пропы) и прорендерить себя во время процесса сборки приложения. Рендеринг на этапе сборки с помощью getStaticProps() означает, что перед размещением компонента Next.js преобразует React в стандартные HTML-страницы, и только после этого они размещаются на хостинге и предоставляются клиенту. Подача HTML-страниц напрямую оптимальна для SEO и быстрой загрузки. Такая методика называется статической генераций сайтов, или SSG.

Пример компонента:

import type { InferGetStaticPropsType, GetStaticProps } from "next";

type Product = {
  id: number;
  name: string;
  price: number;
  isAvailable: boolean;
};

export const getStaticProps = (async (context) => {
  const res = await fetch("https://...");
  const product = await res.json();
  if (!product) {
    return {
      notFound: true,
    };
  } else if (!product.isAvailable) {
    return {
      redirect: {
        destination: "/products",
        permanent: false,
      },
    };
  }
  return { props: { product } };
}) satisfies GetStaticProps<{
  product: Product;
}>;

export default function Page({
  product,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return product.name;
}

getStaticProps() является асинхронной функцией с доступом до context-объекта, который потенциально хранит параметры динамического роута, локали и др. У метода есть 3 варианта возврата:

  • notFound – производит редирект на 404 страницу. В данном примере при попытке получить объект какого-нибудь товара данный сценарий сработает, если API вернет пустые данные

  • redirect – производит смену текущего роута на указанный. В примере условно на страницу продуктов, если продукт найден, но его параметр isAvailable: false указывает на отсутствие данного продукта в наличии

  • props – непосредственно данные необходимые для последующего рендеринга компонента

getServerSideProps - метод, указывающий Next-компоненту выполнить внутреннюю логику и потом передать полученные данные (пропы) и прорендерить себя во время так называемого рантайма. В отличии от статической генерации через getStaticProps(), где HTML генерируется всего один раз и сохраняется для последующих запросов, getServerSideProps() предусматривает создание серверной прослойки на каждый запрос страницы. В таком случае логика внутри метода, передача пропов и частичный рендеринг компонента происходят на стороне сервера на каждый запрос клиентской частью. После обработки на сервере клиент получает базовый неинтерактивный HTML, а также получает JSON-объект пропов и JavaScript-инструкции для добавления интерактивности компоненту. После чего происходит так называемый процесс "гидрации" – выполнения минимального сета инструкций на клиентской части для получения полноценного интерактивного компонента.

Пример компонента:

import type { InferGetServerSidePropsType, GetServerSideProps } from "next";

type Product = {
  id: number;
  name: string;
  price: number;
  isAvailable: boolean;
};

export const getServerSideProps = (async (context) => {
  const res = await fetch("https://...");
  const product = await res.json();
  return { props: { product } };
}) satisfies GetServerSideProps<{
  product: Product;
}>;

export default function Page({
  product,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return product.name;
}

Как мы видим, способ использования мало чем отличается от getStaticProps(), однако, важно четко распознавать случаи для каждого метода. По сути, компоненты с getStaticProps() рендерятся один единственный раз во время билда приложения и отлично подходят как для улучшения производительности, так и для SEO, если они зависят от редко изменяемых внешних данных. Хорошим примером является страничка блога, поскольку подобного рода информация редко меняется в базе данных. В нашем примере некой страницы продукта более подходящим будет использование getServerSideProps(), поскольку информация о каком-нибудь товаре или продукте (цена, наличие и тому подобное), вероятней всего, будет относительно часто меняться и всегда должна быть актуальной. В связи с этим получение обновленных данных на каждый запрос страницы эффективно обеспечивается максимальным выполнением логики во время рендеринга на стороне сервера (SSR). Конечно, в плане скорости получения контента клиентской частью и качества SEO это уступает статической генерации. Однако это [VD1] все еще гораздо лучше, чем выполнение всего процесса на стороне клиента, как в стандартных SPA, написанных на React или других фронт-энд фреймворках.

6.  API Routes

Поскольку Next.js в любом случае имеет Node.js сервер, как мы выяснили выше, Page роутер позволяет легко создавать API эндпоинты в вашем приложении. Разместив файлы в папке pages/api, можно определить бессерверные функции, обрабатывающие HTTP-запросы. Вызов эндроинтов также подчиняется правилу файловой системы, где адрес определяется названиями папок и файлов с API логикой. Данная логика будет исключительно существовать на стороне сервера и не увеличит размер клиентского приложения. Эта возможность упрощает разработку бэкенда и обеспечивает беспрепятственное взаимодействие между клиентом и сервером, позволяя создавать интерактивные веб-приложения. В некотором роде это можно сравнить с MERN стеком, где бэкенд также написан на JavaScript для упрощения коммуникации.

Простой пример:

import type { NextApiRequest, NextApiResponse } from 'next'
 
type ResponseData = {
  message: string
}
 
export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  res.status(200).json({ message: 'Привет всем!' })
}

Эндпоинт вернет JSON-объект со статусом 200.

App роутер

Со слов команды Vercel – «эволюция» разработки на Next.js. Официальный сайт не только рекомендует App роутер как решение для новых проектов, но и советует переход существующих проектов с Page роутера, позволяя совмещать обе модели, тем самым упрощая процесс рефакторинга. Рассмотрим основные изменения и принципы.

1. Файловая структура роутинга

В App роутере файлы компонентов перестают участвовать в определении маршрутизации, поскольку каждый компонент-страница теперь обязан называться зарезервированным словом page. Поэтому всю роль на себя перебирают папки – таким образом компонент по пути app/about/page.tsx будет доступен в браузере по адресу /about. Аналогично синтаксис динамических роутов применяется теперь только к папкам – что-нибудь по типу app/users/[id]/page.tsx  доступно, например, как /users/683. Добавление символа «_» в начале названия папки исключает всю ветку проекта из роутинга.

App роутер
App роутер

2. Зарезервированные имена и роль в формировании страниц

Не секрет, что много веб-приложений имеют глобальные UI-элементы на множестве страниц, или ряду страниц нужна инициализация глобального контекста или состояния. В стандартном React и вследствие в Page роутере это достигалось созданием отдельных компонентов с необходимыми общими UI-элементами или инициализацией контекстов и последующим «оборачиванием» ими других компонентов.  App роутер значительно упрощает этот процесс, предоставляя ряд зарезервированных имен для компонентов, которые при рендеринге страниц автоматически распознаются для формирования нужной иерархии страницы без дополнительного прописывания разработчиком.

Основным таким компонентом является layout. Его роль – предоставить для сегмента файловой системы общий UI и контекст. Рутовый layout находится непосредственно в папке app, формируя «костяк» абсолютно каждой страницы.

Базовый пример:

import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Особенность layout-компонентов в том, что их рендеринг происходит всего один раз за весь жизненный цикл приложения и «запоминается», тем самым повышая производительность приложения. Рутовый layout, например, таким образом становится идеальным местом инициализации глобального состояния с помощью таких популярных решений как Redux, Recoil и Zustand. Аналогично, рутовый layout часто используется для включения в каждую страницу хэдера и футера на UI.

Другие зарезервированные компоненты и их роли:

  • template – схожая роль с layout, однако рендеринг происходит при каждом запросе страницы сегмента;

  • loading – загрузочный UI, который будет показываться до завершения рендеринга запрашиваемой страницы сегмента;

  • error – UI на случай ошибки при обработке запрашиваемой страницы текущего сегмента, использует специальный React-компонент ErrorBoundary;

  • not-found – схожая роль с error специально на случай не распознавания запрашиваемого адреса, аналогично использует ErrorBoundary компонент;

  • page – уникальный UI запрашиваемой страницы.

Для большей наглядности позаимствуем схемы иерархии с официальной Next.js документации:

Структура страницы
Структура страницы

Иерархия страниц на нижних уровнях сегмента файловой системы начинает формироваться внутри всех компонентов высших уровней (кроме уникальных page):

Структура страницы внутри сегмента
Структура страницы внутри сегмента

3. Разделение компонентов на роли. Рендеринг компонентов

Одной из проблем Page роутера была не всегда однозначная грань между совмещением того, что можно было выполнять на сервере и исключительно клиентской логике. App роутер четко делит компоненты на серверные и клиентские. Дефолтно все компоненты являются серверными для стимуляции стиля разработки, при которой максимальное количество логики будет выполнятся по принципах SSG или SSR. Для сигнализирования Next.js, что компонент должен быть обработан на стороне клиента, используется специальная директива в начале файла “use client”. Простой пример клиентского компонента-счетчика:

'use client'
 
import { useState } from 'react'
 
export default function Counter() {
  const [count, setCount] = useState(0)
 
  return (
    <div>
      <p> Вы нажали {count} раз</p>
      <button onClick={() => setCount(count + 1)}>Нажмите меня</button>
    </div>
  )
}

Как видим, это ничем не отличается от обычного React-компонента, использующего useState хук. Но хуки – это часть React API, которая не существует на серверной части, что и требует превращения компонента в клиентский. Примерные требования использования того или иного типа компонентов можно выразить следующей таблицей:

Клиентские компоненты могут внедрятся в серверные, например, в разрезе зарезервированных компонентов, layout обычно является серверным компонентом, тогда как page по необходимости может быть клиентским интерактивным компонентом.

Формирование результирующей страницы сначала происходит в 2 шага на стороне сервера:

  1. React рендерит серверные компоненты в специальный формат данных, который называется React Server Component Payload (RSC Payload).

  2. Next.js использует RSC Payload и JavaScript-инструкции клиентского компонента для рендеринга HTML на сервере.

Затем на клиенте:

  1. HTML используется для немедленного отображения быстрого неинтерактивного предварительного варианта запрашиваемого роута – только для начальной загрузки страницы.

  2. RSC Payload используется для согласования структур клиентских и серверных компонентов и обновления DOM.

  3. JavaScript-инструкции используются для «гидрации» клиентских компонентов и придания приложению интерактивности.

4. Серверные компоненты и стратегии рендеринга

Использование серверных компонентов на вершине структуры страниц позволяет легко получать данные и рендерить компоненты во время сборки приложения или запроса клиентом без использования специальных методов, в отличие от Page роутера. Рассмотрим использование серверных компонентов в разных стратегиях:

1) Статическая генерация (SSG)

Изначально серверные компоненты настроены на статическую генерацию. Любые запросы на бэкенд кэшируются, а их результаты – запоминаются. Пример:

type Product = {
  id: number;
  name: string;
  price: number;
  isAvailable: boolean;
};

async function getProducts() {
  const res = await fetch(`https://...`);
  const products = await res.json();

  return products;
}

export default async function Page() {
  const products = await getProducts();

  return products.map((product : Product) => <div>{product.name}</div>)
}

Next.js расширяет возможности стандартного fetch(), и его использование в серверных компонентах поддерживает специальный параметр cache. Поскольку SSG являет стратегией по умолчанию, все запросы отправляются с cache: 'force-cache', если не указывать этот параметр вручную. По сути, это соответствует getStaticProps() методу из Page роутера.

2) Рендеринг на сервере (SSR)

SSG стратегия легко переходит в SSR при дополнительной настройке fetch(). Передавая параметр cache: 'no-store', мы сигнализируем, что компонент должен проходить рендинг на сервере во время каждого запроса страницы, включающей компонент. Аналогично это соответствует методу getServerSideProps(). Немного поправив предыдущий пример, получаем:

type Product = {
  id: number;
  name: string;
  price: number;
  isAvailable: boolean;
};

async function getProducts() {
  const res = await fetch(`https://...`, { cache: "no-store" });
  const products = await res.json();

  return products;
}

export default async function Page() {
  const products = await getProducts();

  return products.map((product: Product) => <div>{product.name}</div>);
}

3) Стриминг

Стриминг можно назвать специальным видом SSR, используемым для рендеринга серверного компонента, включающего в себя несколько независимых асинхронных блоков или других серверных компонентов. Сначала каждый такой блок оборачивается в специальный React-компонент Suspense, цель которого – показать временный UI, пока внутренний JSX код не завершит рендеринг. Таким образом стратегия стриминга добивается прогрессивного рендеринга HTML на сервере и гидрации компонентов по мере их доступности, постепенно заменяя Suspense-компоненты. Это значительно повышает производительность комплексных страниц по типу дашбордов вебсайтов. Условный пример подобной страницы:

import { Suspense } from 'react'
import { NewsFeed, Weather } from './Components'
 
export default function Dashboard() {
  return (
    <section>
      <Suspense fallback={<p>Загружаем новости. Пожалуйста подождите...</p>}>
        <NewsFeed />
      </Suspense>
      <Suspense fallback={<p>Загружаем погоду. Пожалуйста подождите...</p>}>
        <Weather />
      </Suspense>
    </section>
  )
}

Серверный компонент с примером дашборда[VD1]  включает в себя 2 условных серверных компонента, выполняющих API-запросы для получения новостей и погоды, и, благодаря стримингу, клиентская часть получает частичный UI, который будет обновляться по мере завершения независимых асинхронных операций.

5.  API

Подобно разделению компонентов на серверные и клиентские, API роуты из Page структуры имеют свои аналоги в зависимости от места использования. Если API запрос нужно совершить из серверного компонента, то вместо этого серверный компонент может напрямую совершать операции над базой данных и вести себя как API эндпоинт. В случае необходимости послать запрос из клиентского компонента вследствие взаимодействия пользователя с приложением, App роутер предполагает использование специальных файлов – обработчиков API роутов (Route Handlers). Сам эндпоинт формируется файловой системой. Содержащую папку на верхнем уровне не обязательно называть api, но сегмент должен содержать файл с зарезервированным именем route.js/ts, что дает Next.js понимание, что это исключительно серверный функционал, который не должен быть включен в само клиентское приложение. Сам обработчик должен содержать одну или несколько экспертируемых функций с названием, соответствующим типу запроса из списка доступных для App роутера. На данный момент поддерживаются GETPOSTPUTPATCHDELETEHEAD, и OPTIONS. Простой пример с условным получением данных из MongoDB из обработчика по пути app/productItems/route.ts:

export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/...', {
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY,
    },
  })
  const data = await res.json()
 
  return Response.json({ data })
}

Запрос через fetch(“/productItems”) клиентского компонента вызовет обработчик на стороне сервера с доступом ко всем необходимым элементам авторизации.

Несколько советов для разработки

1)  Сохраняйте лаконичность роутов:

Хотя Next.js позволяет гибко подходить к их наименованию, очень важно сохранять их краткость и осмысленность. Четкие названия и файловая структура улучшают читаемость кодовой базы и облегчают понимание приложения другими разработчиками.

2)  Разделяйте компоненты от логики:

Если вы не используете Typescript, старайтесь давать компонентам расширение .jsx, хоть и просто .js также поддерживается. Это поможет вам и другим разработчикам быстро различать компоненты, составляющие интерфейс, от API роутов/обработчиков, функционала глобального состояния, кастомных хуков и другой абстрактной логики. В случае использования TypeScript только .tsx файлы будут восприниматься как компоненты.

3)  Оптимизация получения данных и рендеринга:

Выберите подходящий метод получения данных исходя из требований вашего приложения. Анализируйте, когда нужен getStaticProps() для статического содержимого, а когда getServerSideProps() для обновлений в Page роутере. Отключайте кэширование запросов только при необходимости в App роутере. Переводите компоненты в клиентские только при четкой необходимости взаимодействия с пользователем или состоянием клиентской части.

4)  Определяйте релевантность API в Next.js

Создавайте API роуты или обработчики только при необходимости. Если у вас уже есть существующий бэкенд, создание дополнительных эндпоинтов в Next.js сервере, которые будут вызывать эндпоинты внешнего сервера, зачастую может ухудшить производительность, не предоставляя ничего взамен. Одной из причин создания API в Next.js приложении при существующем бекенде может быть необходимость проводить сложные манипуляции с данными перед их использованием клиентской частью или отправкой в запросе от клиента. Такое происходит, если ваш внешний бэкенд  предоставляется третьей стороной, и запрос изменений под цели фронтенда не всегда возможен. Но если внешний бэкенд разрабатывается вашей командой, то обычно все сложные манипуляции с данными будут выполняться там (если вы, как минимум, не поссорились с коллегами😁).

Будущее веб-разработки с Next.js

Поскольку спрос на высокопроизводительные веб-приложения продолжает расти, Next.js способен сыграть ключевую роль в формировании будущего веб-разработки. Его способность легко интегрироваться с другими технологиями, а также возможность писать собственный бэкенд, как и использовать внешний, укрепляет его позиции как универсального и перспективного решения. Новый рекомендуемый App роутер продолжает упрощать разработку, устанавливая шаблоны структуры проекта и компонентов для автоматической поддержки частых сценариев. Некоторые скептики считают, что новое решение все еще нестабильно и не всегда совмещается с некоторыми популярными технологиями.  Однако, благодаря активному сообществу и регулярным обновлениям, Next.js имеет все шансы продолжать развиваться и занимать лидирующие позиции в мире современной веб-разработки.

Заключение

Next.js стал устойчивым, адаптивным и удобным для разработчиков фреймворком для создания первоклассных веб-приложений. Его отличительные особенности, легкая оптимизация производительности и простая интеграция с современными технологиями, делают его отличным вариантом для разработчиков, стремящихся обеспечить исключительный пользовательский опыт при соблюдении оптимальных стандартов производительности. Вне зависимости от того, начинаете вы новый проект или стремитесь усовершенствовать уже существующий, Next.js – это мощный инструмент, который должен привлечь ваше внимание в сфере веб-разработки.

Tags:
Hubs:
Total votes 8: ↑5 and ↓3+3
Comments5

Articles

Information

Website
hr.auriga.ru
Registered
Founded
Employees
501–1,000 employees
Location
Россия