Ответ
Один из способов — использовать общий код через монорепозиторий или npm-пакеты. Например, валидацию форм можно вынести в отдельный модуль и импортировать на клиенте и сервере.
// shared/validators.js
export const validateEmail = (email) => /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
GraphQL — другой подход, где сервер описывает схему данных, а клиент запрашивает только нужные поля, уменьшая дублирование логики выборки.
Isomorphic/Universal JavaScript позволяет выполнять один код и на сервере (Node.js), и на клиенте. Например, рендеринг React-компонентов:
// server.js
import App from './App';
const html = ReactDOMServer.renderToString(<App />);
Конфигурация через API — сервер предоставляет клиенту настройки (например, feature flags), избегая хардкода.
Ответ 18+ 🔞
Ах ты ж ёпта, ну и тема подкинули! Слушай, тут же реально боль всех фронтендеров — этот вечный пиздец, когда у тебя одна и та же логика на клиенте и на бэке, а пишешь её дважды, как последний мудак. Прям волнение ебать, каждый раз думаешь: "Ну вот опять, сука, копипаста".
Так вот, есть способ не быть распиздяем. Первый — общий код через монорепозиторий или npm-пакеты. Представь, тебе надо почту валидировать. Ну ты же не будешь на фронте один regex писать, а на бэке — другой, да? Выносишь эту хрень в отдельный файл и таскаешь везде, как царь.
// shared/validators.js
export const validateEmail = (email) => /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
Всё, блядь! И в React-компоненте импортируешь, и в Express-роутере — один хрен работает. Красота, а не жизнь.
Дальше — GraphQL. Это вообще, блядь, хитрая жопа. Тут сервер говорит: "Вот, смотри, у меня есть такие-то данные, схема вот". А клиент ему в ответ: "А дай-ка мне, дружок, только имя пользователя и аватарку, а остальное — на хуй не надо". И дублирования логики выборки — ноль! Сервер один раз описал, а клиенты сами себе запросы лепят, какие хотят.
Ну и классика жанра — Isomorphic/Universal JavaScript. Звучит сложно, а по сути — один и тот же код гоняем и на сервере (в Node.js), и в браузере. Например, рендерим React-компонент на бэке, чтобы пользователю сразу готовая страница прилетела, а не пустой div:
// server.js
import App from './App';
const html = ReactDOMServer.renderToString(<App />);
И клиенту потом не надо весь интерфейс с нуля строить — он просто "оживляет" уже готовую разметку. Ёперный театр, какая экономия времени!
А ещё можно конфигурацию через API кидать. Не надо в коде клиента фичи-флаги хардкодить, как последний ламер. Сервер говорит: "На, братан, вот тебе настройки, какие кнопки показывать". И если что-то поменялось — не надо пересобирать весь фронтенд, просто бэк новый JSON отдал. Удобно, блядь, до охуища!
В общем, варианты есть. Главное — не дублировать логику, как Герасим Муму, а то потом сам себе ебальник разобьёшь.