Изоморфные React-приложения: производительность и масштабирование



    Денис Измайлов ( DenisIzmaylov )


    Всем привет! Вкратце расскажу о себе. Я — Денис Измайлов. Последние 5 лет сосредоточился на JS-разработке, делал много Single Page Application, highload, React, выступал на MoscowJS несколько раз, каммитил webpack и т.д.

    Сегодня хотел бы поговорить вот о чем: почему от Single Page Application в его классическом виде стоит отказаться; как изоморфные приложения отразятся на Вашей зарплате; и что вы будете делать на этих выходных?



    Было бы идеально, если бы у вас уже был какой-то опыт работы с React 14, с webpack, понимали, как работает ES6, что-то делали с express/koa, и хотя бы образно представляете, что такое изоморфные приложения, они же универсальные.

    Часть 1.

    Web стал очень большим. Сейчас разработка под web стала уже тонкой гранью между наукой и искусством.



    Раньше было достаточно просто: сделал какой-то скрипт на сервере, добавил пару JS и отправил в продакшн, все работало.



    Браузер делал запрос к серверу, а сервер делал абсолютно все, и отдавал какую-то HTML-страницу.



    CSS, JS — это было опционально, практически не необходимо. Это работало. Но потом начали выделятся Single Page Application (SPA).



    Код на клиентской части стал разрастаться. Сервер уже стал исполнять маленькую роль. Буквально проверялось, существует ли у нас страница, необходима ли авторизация, есть ли у нас доступ к нему?



    И сервер отдавал уже небольшой HTML, CSS и огромный JS bundle.



    Плюсы здесь достаточно большие, т.е. легко начать писать — посмотрел доклад про webpack, настроил, сделал маленький HTML, подключил Redux, React, и оно все заработало.



    • Богатый функционал. Мы все прогружаем сразу в случае Single Page, и нам не надо заботиться о том, что у нас чего-то нет. Мы можем включать туда абсолютно все, что угодно — картинки, изображения, можем даже видео засунуть внутрь bundle, и это будет работать.
    • Быстро дорабатывать, достаточно легко — нам не надо будет заботиться ни о какой оптимизации, у нас все работает, у нас все супер, все классно, мы можем просто добавлять-добавлять-добавлять.
    • Отзывчивый UI — соответственно, у нас все загружено, и не надо ничего догружать.
    • Это все очень удобно кэшировать. Когда мы загрузили раз какой-то bundle, он у нас уже всегда сохраняется в кэше в браузере и следующий раз у нас быстро загружается.

    И не одного минуса? Минусы, есть.



    Во-первых, из-за того, что bundle очень большой, порой достигает 3-5 Мб, его очень долго в первый раз загружать. Т.е. часто, когда заходишь в первый раз на сайт, на ресурс какой-то, либо после обновления на прошлый, видим колесико, которое многим не нравится. Т.о. первое обращение к приложению происходит достаточно долго. Исполняется это тоже не быстро. И память это кушает прилично.



    Сложность поддержки. Кто поддерживал большие Single Page Application приложения, тот знает, что часто какие-то плагины, которые мы добавляем на некоторые, даже отдельные страницы, могут повлиять на любые другие страницы. Т.е. мы это не можем качественно проконтролировать. И memory leak. Мало кто тестировал, но когда приложение большое и оно у нас работает долго во вкладке, то часто бывает, что память разрастается, и все у нас почему-то начинает виснуть. 3-4 часа прошло и… Но мы это не тестируем, как правило.



    Соответственно, долгая загрузка, сложность поддержки, пустая страница, один url (позже объясню, почему это минус) и старые браузеры. Недавно проводили исследования и смотрели, что даже сейчас в наш 2015-2016-ый года, уже, казалось бы, все браузеры должны были обновиться, но нет. 5% — это 12-ая Опера и 7-8-ой IE, и 6-ой даже присутствует. Буквально на прошлой неделе смотрели.

    Разве это минусы? Для бизнеса это минусы.



    Долгая загрузка — это потери UX, в который вкладываются большие деньги. Сложность поддержки — это риски. Это риски как не уложиться в срок, так и вылезти за бюджет. Пустая страница — это проблемы SEO. Тут у многих может возникнуть такое возражение: есть же пререндеры, наше Angular-приложение может быть легко отрендеренно заранее. Но это, по сути, хак. Там очень мало возможностей в связи с этим. 1 URL — это проблема SMM. SMM — это значит, что пользователь не может расшарить спокойно вашу страницу в социальных сетях, она будет вести на одну страницу. И соответственно, если эта страница не исполнится на старом браузере, то, если у вас нет полифилов, от которых сейчас многие отказываются, то человек на старом браузере в этих 5%, увидит пустую страницу. Все это для бизнеса расходы.



    Что делать? Мы можем сейчас взять лучшее из этих двух миров и сделать это в виде изоморфных приложений.



    В 2011 году Чарли Роббинс сформулировал это так, что под изоморфным мы подразумеваем тот код, в котором каждая взятая строка может исполнятся как на сервере, так и на клиенте. С небольшими исключениями.



    Здесь очень важно сейчас сосредоточиться и понять, потому что возникает вопрос: а как делить, а как организовать изоморфные приложения? Я попытался сформулировать так, что вот эти части — шаблоны, сервисы, статичные файлы, Actions, Reducers, оно у нас выделяется в универсальное абсолютно, а небольшие стартовые вещи — это может быть клиентский файл, или админка, т.е. то, что инициализирует роутер — это у нас уже может быть для серверной части своя, для клиентской части — своя.



    Выглядит это все так, что у нас есть браузер, есть фронтенд-сервер (выделяется отдельно), и есть старый привычный бэкенд-сервер. Это может быть даже база данных без какого-то сервера. Браузер обращается к фронтенд-серверу, тот может обратиться за данными куда-то к старому серверу либо к базе данных, получить данные.



    Далее он может сформировать полный HTML, отдать какой-то критичный CSS, который необходим только для рендеринга этой страницы, а не, как в случае с SPA, для рендеринга всего приложения, которое может вполне вам понадобиться.



    И в конце отдать JS bundle. В этот момент у нас будет собран на клиентской части и поднят фронтенд-клиент, который в свою очередь дальнейшую коммуникацию может осуществлять с бэкенд-сервером предыдущим, допустим, через RESTful API.



    Итого, мы имеем единую среду исполнения, общую кодовую базу и полный контроль, потому что, когда у нас бэкенд-сервер, например, на Java, фронтенд-разработчик достаточно проблематично может повлиять или запушить какие-то изменения на сервер. И опять же, имеем экосистему, т.е. node package manager.

    Как это реализовать в жизни? Через Sever-Side Rendering.



    Суть такая, что у нас сборка при Server-Side Rendering, сборка всего приложения на React’е, например, происходит на фронтенд-сервере, на Node.js. В то же время мы получаем моментально отображение, т.е. у нас отрендерилось приложение, мы сразу его получаем в виде HTML — еще до загрузки JS. И пользователь видит его моментально. Т.е. при первом же обращении. А когда загрузится JS, у нас React просто добавит обработчики события, а это все происходит достаточно быстро.

    Код на серверной части выглядит вот так:



    Т.е. у нас есть некий рендерер React’овский, это уже 14-ая версия, и есть то самое наше приложение. Т.е. это компилируется в строку и отдается. Понятно, что у нас внутри application уже завернуты все роуты и там, т.к. у нас тот же самый код исполнится, мы уже знаем, что у нас есть какой-то путь, что-то мы должны отобразить для этого пути, у нас будет отображено необходимое нам дерево.



    В итоге, что мы имеем? Пользователь мгновенно видит страницу, и у нас не происходит каких-то дополнительных запросов на получение данных, потому что для ее отображения у нас уже все данные собраны и внутри поставлены, где надо. И страница может запускаться даже без JS. Это очень важно, чтобы работать, на том же legasy-браузере из тех 5%. Пусть у них там что-то будет не функционировать так классно, как это сейчас на последнем хроме, но оно будет более-менее работать. И полноценная URL-навигация, и в то же время мета-теги, т.е. это все то, что позволит нам (для продуктовых историй это особенно важно) шарить, делится какими-то ссылками на отдельно взятые страницы гораздо более эффективно, чем как это принято сейчас с хэшом. И в то же время у нас имеется сохранение всех возможностей, актуальных для JS, которые мы имеем.

    Часть 2. Производительность и масштабирование. Масштабирование тут не по нагрузочной производительности, а про функциональное масштабирование — как мы будем расти.



    В случае Server-Side Rendering все супер, когда у нас все данные уже есть. Все понимают, что Node.js — это однопоточная история, т.к. как обычный JS и, соответственно, все супер, когда у нас данные есть, и мы в одном потоке может все это отрисовать.



    Но что, если нам эти данные необходимо получить? Сделать какой-то запрос при этом, мы не имеем право заблочить поток, текущий event loop.



    Тут у нас есть три основных способа: необходимые данные изъять вручную и потом отдать как на входящий state; использовать Facebook Relay; и я разрабатывал плагин для Redux-catch-promise.



    Вручную для каждой страницы — там все достаточно просто, мы получаем state из базы и потом просто отдаем в renderToString. Ничего у нас такого не меняется, но в этом случае нам для каждой страницы приходится придумывать, а как мы на сервер будем эти данные получать? Мы уже не можем просто взять и добавить страницу, нам придется эти данные как-то извлекать, лезть, дополнительные сущности вводить в проект. Тоже не всегда удобно.



    Facebook Relay, который они презентовали несколько месяцев назад — это фреймворк, который позволяет декларативно задавать в компонентах специальные запросы на получение данных. Там достаточно интересная история, т.е. вы декларативно указываете запрос, какие данные вам необходимы, условия, что они завязаны. Relay эти все данные аккумулирует и потом разом кидает на сервер, и вы их получаете разом. Т.е. это происходит батчинг запросов, о котором мы говорили. Единственная проблема, что на сервере это пока не доступно, т.е. server-side, но в первом квартале 2016-го года Facebook обещает уже реализовать это, и будет все работать. Там ссылка на GitHub issues, можете наблюдать.



    Redux-catch-promise — это небольшой лайфках, который я сделал, работая для одного проекта. Что такое Redux? Я рассказывал на MoscowJS про него. Это state-контейнер для React. По сути, это замена Flux, гораздо более удачная замена как показывают. Ссылка на выступление есть. Redux-catch-promise — это именно middleware, т.е. плагин для Redux.



    Что он делает? Мы вешаем callback для захвата Promise-экшнов в потоке и делаем рендер приложения. При рендере компонента у нас делается запрос, т.е. мы отправляем экшн на получение данных и диспатчим ему в ответ Promise. Этот Promise мы отлавливаем на верхнем уровне, где мы рендерим приложение, и в итоге у нас получается коллекция Promise’ов. Дождавшись ее разрешения, мы рендерим повторно приложение с полученными данными. Тут получается достаточно удобно, некий компромисс между ручным получением и тем, что сейчас реализовано в Relay.



    Ссылка на GitHub здесь, там есть пример, может быть, немного устаревший, у нас все очень быстро меняется.

    Производительность — вторая часть.



    Когда начали смотреть, насколько быстро Server-Side рендерится на обычном MacBook’е…

    Чтобы понимание было — страница занимает 56 Кбайт, выглядит она буквально в 4 экрана, небольшой профиль…











    Со всеми данными, возьмем ab по тестированию, запрос полноценный. Вышла 61 мс.



    Немного непонятно, много это или мало. Допустим, тот же самый, если Hendlebars мы сделаем, то это будет 8 мс.



    Думаю, так разница очевиднее, что не супер.



    Начинаем искать что-то, смотрим, идем в Google — там ничего конкретного. Пробуем спросить Twitter, тоже все молчат, ретвитят, но никаких ответов мы не находим.

    В то же время попробуем поставить NODE_ENV в production.



    Запускаем и — бац! — практически в два раза быстрее. Супер, интересно. Вроде лучше, но все еще не торт.



    Идем дальше. Посмотрим, в GitHub issues полазим.



    Найдем интересное такое «Server rendering is slower with npm react». Там дается решение, что необходимо подключать, а не делать просто импорт react, подключать файл явно react.min.js.



    Ок, попробуем. Создадим для теста некий node_modules. Для чистоты сделаем так. Тут мы явно в случае production используем min.js. И в итоге, как это изменило результат? Запускаем.



    Получилось даже медленнее.



    Слайд, что тест провален.

    Как оказалось, тот совет хорошо работал для прошлого React’а, а для 14-го не работает. Общая картинка выглядит примерно вот так:



    Т.е. 8 мс занимает Hendlebars, все остальное — это, собственно, если production мы указываем, и фиолетовый — если мы используем js min. Всего лишь на 39% меньше.

    Какие-то дальнейшие глобальные изыскания — никакого хака в Server-Side Rendering мы не увидим. На уровне React это все оптимизировать особо некуда, сейчас пока. Есть только достаточно hardcore’ные пути.



    Если глобально разделить это, то использовать некие Precompilation и Cache, это разделить Rendering. И использовать недавно вышедший плагин React DOM Stream. Использовать Facebook BigPipe — это очень интересное расширение от Facebook, которое уже давно используется. И HAProxy — это больше к devops, другим секциям доклада.



    В чем суть Precompilation? По сути, если мы будем отталкиваться от того, что UI — это результат выполнения некой функции от state — f(state), где f — это React Component, а state — это может быть, допустим, только путь, если это какой-то сайт или дерево страниц, или это может быть у нас завязано на еще что-то. Мы можем достаточно легко по этому ключу кэшировать наш HTML, который мы рендерим.

    Простое решение — это просто использовать redis. Если нам нужно, чтобы у нас сразу был быстрый ответ, мы можем использовать redis, плюс очереди, плюс воркеры. Т.е. мы показываем какое-то колесико, загрузку делаем частичную, а в фоне у нас воркеры рендерят при каждом запросе наш компонент. Тут мы получим, где First render, то, что у нас, по сути, в этом случае страница будет отдана моментально, все срендерится у нас в фоне, и при следующем запросе мы уже можем отдать закэшированную часть компонентов.



    Rendering Separation. Linkedin провел исследования, и они пронаблюдали, что большинство из их страниц (длинных, особенно) не нуждаются в полном серверном рендеринге, достаточно показать только первую часть — видимую. А все остальное мы уже можем догружать отдельно, допустим клиентским JS. И у них в большинстве случаев клиенты видели только первую часть, никто не прокручивал, т.е. таким образом мы можем первую часть на сервере отрендерить и сразу ее отдать, а дальнейшую часть уже программно загрузить через JS. Это дает достаточно большое преимущество в перформансе и в ресурсах.



    React DOM Stream — это уже следующее расширение — это недавно опубликованный проект. Человек (aickin) форкнул React и реализовал там технологию преждевременного сброса, как http обычного, т.е. вы рендерите-рендерите, а потом разом все отдает. В данном случае он смог реализовать технологию, когда у вас сразу по мере рендеринга компонента, а не в виде html-кода, они сбрасываются в сервер stream. Получается достаточно интересный эффект производительности. Время до первого байта сокращается уже на 65%, т.е. это еще почти в 2 раза, и до последнего байта — на 37%. Если совмещать эти два подхода, получается достаточно хорошо… Единственный минус, что это затронуло сам React, его необходимо было немного доработать, поэтому он там использует форк, и это не тот React, который официальный. Там сейчас идет дискуссия по этому поводу, можете наблюдать, скоро будет готово.



    Facebook BigPipe. Очень классное расширение. Они его сделали очень давно, года 2-3 назад. Это когда сборка страницы осуществляется в процессе загрузки. Т.е. у нас все загружается параллельно, от этого у нас получается устойчивость к ошибкам. Суть такая: у нас есть страница, и у нее есть определенные части, которые мы можем выделить и загружать уже в процессе. Т.е. в процессе загрузки страницы мы формируем вызов необходимых для этих блоков JS, CSS, и у нас данные сами загружаются, через какой-то json pipe.



    Получается, что мы при первом рендере видим такую, почти полную страницу. Мы можем увидеть даже что-то важное, как Yandex, например, делает. В Yandex, когда вы вводите поисковую фразу, нажали enter, у вас сначала отобразится верхний toolbar с вашим запросом, и только потом догрузятся сами результаты выдачи. Здесь процесс изображен.



    HAProxy. Это немного про DevOps, но думаю, у всех сейчас есть доступ к DevOps-специалисту, либо еще что-то сами потом можете настроить. Суть такая, что на продакшне лучше поднимать несколько нод и между ними уже циркулировать.

    В заключение хочу привести несколько полезных материалов:

    1. Supercharging page load (100 Days of Google Dev) https://youtu.be/d5_6yHixpsQ
    2. Making Netflix.com Faster http://techblog.netflix.com/2015/08/making-netflixcom-faster.html
    3. New technologies for the new LinkedIn home page https://engineering.linkedin.com/frontend/new-technologies-new-linkedin-home-page
    4. Improving performance on twitter.com https://blog.twitter.com/2012/improving-performance-on-twittercom
    5. Scaling Isomorphic Javascript Code https://blog.nodejitsu.com/scaling-isomorphic-javascript-code/
    6. From AngularJS to React: The Isomorphic Way https://blog.risingstack.com/from-angularjs-to-react-the-isomorphic-way/
    7. Isomorphic JavaScript: The Future of Web Apps http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/
    8. React server side rendering performance http://www.slideshare.net/nickdreckshage/react-meetup
    9. The Lost Art of Progressive HTML Rendering https://blog.codinghorror.com/the-lost-art-of-progressive-html-rendering/

    А в качестве рекомендаций — присоединяйтесь к сообществу MoscowJS и следите за обновлениями.



    Там постоянно у нас что-то интересное происходит.

    Самое важное — это улучшайте английский, приходите на англоязычные доклады и «не читайте советских газет». Читайте оригиналы и технические блоги. Допустим, те же компании — Linkedin, Facebook, Netflix — они очень актуальные вещи пишут. В Twitter вы всегда можете увидеть все эти анонсы. И Twitter, GitHub сейчас являются, наверное, основными вещами, с помощью которых вы можете держать руку на пульсе и понимать, что происходит в мире фронтенда.

    Хочу две цитаты дать, которые мне очень понравились в этой связи:



    «Большинство проблем алгоритмов можно решить сменой структуры данных». Это Андрей Ситник сказал в одном из выпусков РадиоJS. И в одном из видео: «Changes is our work», т.е. изменения — это наша работа. Это Jake из Google сказал.

    Надеюсь, я сейчас ответил на вопрос, почему от классического Single Page Application необходимо отказаться и куда стоит двигаться. Это на самом деле не так сложно, как кажется, и я призываю дальше двигаться в эту сторону.

    Контакты


    » DenisIzmaylov
    » github
    » twitter

    Этот доклад — расшифровка одного из лучших выступлений на конференции разработчиков высоконагруженных систем HighLoad++ специальной секции «Производительность фронтенда».

    Также некоторые из этих материалов используются нами в обучающем онлайн-курсе по разработке высоконагруженных систем HighLoad.Guide — это цепочка специально подобранных писем, статей, материалов, видео. Уже сейчас в нашем учебнике более 30 уникальных материалов. Подключайтесь!

    Ну и главная новость — мы начали подготовку весеннего фестиваля "Российские интернет-технологии", в который входит восемь конференций, включая Frontend Conf. Это профессиональная конференция для разработчиков высоконагруженных систем. А Денис, кстати, входит в её Программный комитет.

    Конференции Олега Бунина (Онтико)

    420,00

    Конференции Олега Бунина

    Поделиться публикацией

    Похожие публикации

    Комментарии 55
      +3

      Спасибо за обзор.


      "как изоморфные приложения отразятся на Вашей зарплате" — хотелось бы послушать и про это.


      Про большой размер Bundle-JS: на самом деле можно всё распилить на небольшие чанки (отдельные куски js-кода) и грузить их динамически на клиенте через SystemJS. Webpack 2 нам поможет.


      Пока что у React-а весьма плачевная ситуация с роутингом — с 2013 никто так и не осилил сделать всё по уму. Вся надежда на React Router v4. Но пока что там всё очень сыро и недоделано.


      А в целом про изоморфные приложения, что хочу добавить: их разработка требует весьма больших компетенций, очень легко где-нибудь облажаться, нужно разбираться в десятках современных инструментов и использовать некоторые подходы к разработке, которые плохо ложатся в головы программистов на JavaScript, например, практически нельзя использовать синглтоны и глобальные переменные в изоморфном коде, а те, кто привык писать клиентский код, лепят их где попало, ибо привыкли, что всегда один экземпляр клиента.

        0
        Про большой размер Bundle-JS: на самом деле можно всё распилить на небольшие чанки (отдельные куски js-кода) и грузить их динамически...

        Ну на самом деле это далеко не волшебная пилюля. Например у меня в единственном проекте на Reat при загрузке отображается либо страница логина, либо главная страница. В любом случае для работы эти страниц надо загрузить: React, React Bootstrap, lodash, React Router, Redux. Это самые большие библиотеки в проекте и именно они составляют около 70 процентов бандла. Так что в динамическую загрузку получиться отправить только 30% бандла (а то и меньше).

          0
          Даже при таких раскладах на низких скоростях заметна разница между загрузкой страницы логина за (грубо) 4 секунды и потом главной страница за ещё 2, чем загрузка страницы логина за 7 секунд и дальше без загрузки.
        0
        Подозреваю что скоро изоморфные приложения можно будет делать на C++/Rust, подозреваю что это будет самый скоростной (в плане производительности, а не скорости разработки :-) вариант
          0

          Хоть сейчас берите Emscripten и пишите.

            +5
            изоморфные приложения на с++ 20 лет назад называлось CGI.
            И может я че-то не понимаю… Раньше все скидывали на клиент, чтобы сервер меньше считал. А теперь типа все назад на сервер, потмоу что ОДИН клиент не тянет. А сервер значит 1000 клиентов — легко.
            И вот что ещё странно. На клиенте утечка памяти есть, а на сервере на 1000 подключений на томже самом стеке технологий не будет… Странно.
            А как дебажить эти ваши изоморфные приложения???
              0

              Давайте я вам расскажу. Приложение на клиенте выполняется все время: там всякие таймеры, бэкграунд события, работа со стейтом и тд.


              Сервер сайд рендеринг конвертит HTTP запрос в HTML и отдает его клиенту. Все! Поэтому он и тянет 1000 клиентов и утечек там не происходит. Задержка для обычных SPA происходит, потому что надо выкачать JS, обработать его и запустить. Здесь и проходят потеря времени. Изоморфные приложения дают контент юзеру раньше, чем обработается JS, что улучшает UX и делает ваш ресурс SEO-friendly.


              Дебажать также просто — у вас код общий на клиенте и на сервере на 90+%. Так что разницы между SPA и изоморфной частью нет особенно. Тут более подробно — https://habrahabr.ru/post/309958/

                0

                А почему это все на клиенте создает утечки? Может надо бороться с утечками нормальным кодом, который не течет? При чем утечки в контексте статьи? Или течет какая то либа, которая отвечает за стейт? Ну так выкиньте ее на свалку.

                0
                Вы не правы. Суть сферических изоморфных приложений в вакууме в том, что они могут исполняться и на сервере и на клиенте. Ни 20 лет назад, ни сейчас C++ код нельзя выполнить в браузере.
                  0
                  Вообще говоря, если не ограничивать клиентов браузерами, то изоморфные (в какой-то степени) приложения появились чуть ли не сразу с появления клиент-серверной архитектуры. Особенно в сферах, где нормальным была длительная работа при отсутствии связи с сервером. Я такие писал ещё в 90-х.
                    +3
                    Ага. Начинаем определяться с терминами.
                    Суть статьи в следующем.
                    Придумываем 4-хзвенную технологию.
                    Звено 1 — база данных
                    Звено 2 — сервер, которые генерит данные из базы наружу. Сейчас это можно слово «RAST»
                    Звено 3 — сервер, который генерит шаблон страницы в HTML с помощью js. Это костыль данной технологии — генерацию страницы уже не знаю куда приткнуть: typescript(чтобы можно было больше 3-х файлов написать коллективом более 2-х человек) + babel (потому что нужен прогресс ради прогресса без понятного результата) + framework1-2-3 (костыли для компонентной разработки, потомоу что HTML убог и примитивен). Если выйти за рамки JS, то это все всякие разные PHP/Python/CGI спокойно делают это последние 20 лет без особых заморочек.
                    Звено4 — браузер, который получив шаблов HTML, подгружает скрипты и страница работает.

                    Всяя суть статьи в том, что звенья 3 и 4 вы можете сделать в браузере. По своему желанию — или так, или так.
                    Только особенность технологии JS — если сделать все браузере, то он ставит современные компы на колени (напримет тотже atom за 3-4 час а рабоыт выжирает несколько Гб оперативки на паре открытых файлов по 10 кб).
                    Итогог, ИЗОМОРФНЫЙ = костыль JS.
                    Если это слово убрать, то PHP/Python/C++/Pascal + JS — тоже можно, только модные слова типа React, Angular, ES6 — проходят мимо и остается просто работа.

                    Кстати, судя по статьям на хабре, технология фремворков JS деградирует — ответа, на чем писать, нет даже приблизительно, есть лишь разные варианты БДСМ.

                    ЗЫ. Подсказываю, как и коментатор ниже — проблема в браузере как стеке технологий.
                    0
                    > изоморфные приложения на с++ 20 лет назад называлось CGI.
                    Серверная часть веб приложений на С++ и сейчас есть, это не обязательно С++. Экзотика это С++ в клиенте через наступающий WebAssembly.
                  +6
                  В большинстве случаев не согласен с автором.

                  — Грамотный подход к структуре приложения. Не надо делать один большой бандл. Разбивайте приложение на пакеты и модули. Подгружайте все остальное по мере необходимости. И не будет никаких проблем с пустой страницей. К примеру у меня 10 пакетов, каждый их которых содержит 5-7 модулей. А для главной нужены только два три модуля из пакета. А когда происходит навигация — догружаете нужное.

                  — Проблемы СЕО. Создаем АПИ для робота и отдаем нужный контент. Да, это хак, но снимает кучу проблем. Да приходится поддерживать две точки входа, но еще ни разу не испытывали с этим проблем.

                  — Один код для сервера и клиента не всегда хорошо, а можно сказать плохо. Возьмем на примере микросервисную архитектуру, где микросервис может быть написан на Java/JS/Python/Ruby/PHP. Кроме JS ваш код нигде больше не пригодится. И что делать, если решили переписать, к примеру один микросервис с Node JS на Java? Приплыли с изоморфом. Пусть лучше каждая часть делает то, что хорошо умеет.

                  — 5% для бизнеса это крах? Ребята, оставьте «дедушек» умирать. Пусть отойдут в мир иной. И задумайтесь над поддержкой кода для «старья». Иногда овчинка выделки не стоит. Для себя давно вынес решение — никакой поддержки старых браузеров, что приносит значительную экономию по времени, а так же позволяет использовать более новые технологии без лишних полифилов.

                    +2

                    Обычно когда речь про изоморфные веб-приложения, это не про API. Это исключительно про UI-ную часть.
                    API можно на чем угодно писать и дробить его на сколько угодно микросервисов.

                      0
                      Я, прочитав статью, поначалу расстроился за свой любимый метеор. А после вашего коммента, думаю напрасно :) Тоже, как и в комменте выше про разделение нагрузки между клиентом и сервером, очень смущает этот SSR. Хотя и в метеоре он тоже есть (пакетом с префиксом meteorhacks...). Обычно он нужен не более чем для отправки писем.

                      Было бы интересно ознакомиться с какими-нибудь материалами по SEO-адаптации одностраничных приложений, поделитесь опытом или ссылкой, пожалуйста.
                        +1
                        Да все довольно просто. Принимаете все запросы на один урл. Далее анализируете HTTP_USER_AGENT. Если пришел паук, отправляете его на вторую точку доступа, которая подготавливает страницу для него только с нужным контентом. Иначе остальное уже обрабатывается как SPA, только нужный урл уже отдаете клиенту, что бы он вызвал нужный роут итд.

                        Для паука можно подготовить один шаблон для всех страниц — картинка, заголовок, контент (или массив контентов).
                          0
                          Понятно, спасибо. У меня сначала была мысль не по юзер-агенту, а через роботс.тхт и сайтмап, но это уже вариации на тему.
                            0
                            Вот тут подумалось (в контексте пакета meteorhacks:inject-initial).
                            Почему бы не отдавать сразу в html скрытый html-контент для индексации?

                            В целом если поразмыслить, я написал 4-5 SPA. Это именно Applications, и хоть в одном бы ешкин кошкин понадобилась бы SEO-оптимизация… так нет же… всё самое вкусное, в назовем так, «личном кабинете».
                              +1
                              По поводу скрытого контента — не знаю. Не пробовал с такой стороны посмотреть. Возможно, кто пробовал дадут более верный фидбек об этом.

                              По поводу вкусняшек — самое вкусное содержит приватные данные пользователя, который бы не хотел публиковать их наружу. Если развивать тему, то SEO оптимизацию имеет смысл делать, в рамках SPA, только на блогах или магазинах к примеру. Да и там собственно логики кот наплакал.

                              Возьмем за основу магазин. Страница списка товаров. Страница товара. Корзина. Навигация. Поиск. Да собственно и все. Логики почти ноль, как и работы над проектом. И вообще не интересно работать с таким проектом.

                              Другое дело если это ERP или CRM системы. Со своей админкой, кабинетами, профилями, правами доступа итд. Вот тут SEO и нафиг не нужна. Зато здесь уже надо уделять внимание структуре проекта, оптимизации кода, бизнес логике итд.
                                0
                                Да, согласен. Магазины и блоги — это не приложения, там вся мощь клиентского рендеринга и не нужна особо. Возможно даже и для личного кабинета банка (но тут не факт).

                                Что касается отдачи скрытого контента — надо знать URL'ы, а в метеоре обычно роутинг клиентский. Так что тоже не особо вариант. В любом случае SEO-оптимизировать, скажем, канбан-доску и прочий функционал, связанный с пользовательским контентом, да, нет смысла.
                        +4
                        Коллеги, а мне для понимания расскажите, пожалуйста — почему минусуете текст? Не расшифровывать тексты по фронтенду? Или что в этом конкретном тексте плохо?
                          0

                          Хороший текст, я даже для себя немного нового узнал. Правда я плюсанул :)

                            +2

                            У темы "изоморфных веб-приложений" много ненавистников. Увы. Хотя легко понять почему, всё очень сложно программируется, сотни статей на эту тему, а на выходе всё равно у всех получается страшный клиент весом 3 мегабайта.
                            Но когда-нибудь скоро все проблемы и подходы к их решению будут стандартизированы, и мы будем иметь легковесные изоморфные single page application.

                              –1

                              Изоморфность в контексте Реакта — тупиковый путь в принципе. И проблема даже не в истинной сложности (о которой стыдливо умалчивают в восторженных статьях) и весе, а в том, что на самом деле эта вся возня не дает никакого ощутимого профита по сравнению с иными подходами. А если говорить о будущем — вспомните лучше о веб-компонентах: близкое знакомство с ними ставит мозги на место и хорошо лечит Реакт-головного-мозга.

                              +3
                              Холиварная тема в принципе. До сих пор не закончился толком холивар на тему где лучше рендерить HTML/DOM — на клиенте или сервере, а тут предлагается рендерить и там, и там. У практиков сразу всплывают минусы обоих способов.
                                0
                                Наверное маркетинговый bullshit и хипстота :-)
                                +6

                                В теории все замечательно. На практике, уже с интернетом 1 Мбит пользоваться Web очень некомфортно. Когда используешь телефон в области слабого приема (Edge, а такие места встречаются очень часто даже в Московской области), можно даже не пытаться загрузить многие сайты — все равно загрузка этих гигантских бандлов либо прервется по таймауту через несколько минут, либо, что более вероятно, устанешь ждать.


                                За границей, где слабый Wifi или медленный и дорогой мобильный интернет, современный Web превращает жизнь в кошмар. В прошлом году мне надо было отправить перевод через Интернет-банк за границей. Я потратил на это полдня, но так и не смог дождаться загрузки интерфейса банка. Загрузка постоянно отваливалась по таймауту, все эти огромные бандлы грузятся одним куском и не кэшируются.


                                "Кэширование? Это какая-то технология 1999 года? У нас нет времени на это старье, мы тут переписываем свое изоморфное Redux-React супер приложение с React 14 на React 15. На наших гигабитных каналах это уменьшает время отклика на 0.001 с — гигантское преимущество, пускай и придется переписать 80% кода. Отстаньте со своим кэшированием". Как-то так получается… :(

                                  +1

                                  Хипстота же, технологии ради технологи. Создаем мельницы, а потом героически с ними боремся, чтобы было о чем рассказать на следующем афтерпати за бокальчиком смузи. Загрузку кода современного жсера можно ускорить вдвое, если выкинуть весь код, который был подключен просто чтобы не выпадать из трендов на следующей конференции.

                                    0
                                    А можете уточник за какой границе вам попался такой слабый вайфай и дорогой мобильный интернет?

                                    Право, сейчас за границей и ситуация строго наоборот — вайфай везде и бесплатно, 4G на телефоне и стоит копейки.
                                      +1

                                      Ну, конкретно проблема с банком была в Индии. Я знаю, что в Индии едва ли не самый медленный интернет в мире. Но на мой взгляд это не оправдывает хипста-сайты, которые сейчас везде. Если в Индии их просто загрузить невозможно, то в России, например, это просто некомфортно. Во-первых, очень долго. За городом у меня интернет 1 Мбит, и очень многие сайты ощутимо тормозят. Более быстрый интернет обойдется мне значительно дороже.


                                      Многие тарифные планы для мобильных имеют ограничения по трафику. Не знаю почему, но по опыту все эти React-Redux-куча-прочих-buzzwords приложения каждый раз загружают эти бандлы заново. Может быть React требует внесения изменений в приложение каждые полчаса. Не знаю. Но трафик это подъедает заметно, и скорость загрузки тоже сильно страдает.


                                      В принципе понимаю, что нет в жизни счастья, и надо раскошеливаться на новый интернет-канал на даче, лучше ситуация не станет. А за границей надо планировать все так, чтобы не понадобилось заходить в интернет-банк. Просто удивляет, что все это преподносится как супер-дупер технологии, которые улучшают время загрузки и отклика, а по моему опыту как пользователя — все совсем наоборот. Страницы стали тяжелыми, медленными и хрупкими (то есть, если скорость канала чуть ниже какой-то критической, субъективно менее 1 Мбит, то все эти SPA начинают подвисать, глючить, ломаться).

                                        0
                                        Собственно для этого и придумали изорморфные приложения: сначала грузится HTML, а потом уже все эти новомодные фичи
                                          0

                                          Ну, в случае с интернет-банком (а также во многих других), это бесполезно. Все равно, пока не догрузятся гигантские бандлы, ничего в банке сделать будет нельзя. А они не загрузятся, потому что загрузка прервется по таймауту.


                                          А даже если и загрузятся, то ни одно из новомодных приложений не проверятся на то, как оно будет вести себя на плохом канале связи. В итоге нередко бывает, что кнопки не нажимаются, формы не работают, и т.д. и т.п., потому что приложение предполагает, что любая коммуникация с сервером завершается успешно (потому что у разработчиков сервер на той же машине, или рядом, на 10 Гбит канале), шлют по сети гигантские JSON-блобы, которые опять же либо теряются, либо приводят к таймауту, а эти ситуации никак не предусмотрены в приложении. Некогда их предусматривать, новая версия Redux вышла. Или FooBarQuux, который в тыщщу раз круче, чем Redux.

                                            0
                                            А с чего вы взяли, что в случае нормального изоморфного приложения нужно ждать загрузки бандла? А как же Progressive Enhancement?
                                              0

                                              Я это взял с того, что часто нахожусь в местах, где медленный интернет. Там все эти модные SPA работают из рук вон плохо. Progressive Enhancement — все понимают, что это хорошо, но никто не делает. Например, один из российских банков отображает страницу ввода пароля до загрузки бандла, но если до полной загрузки ввести пароль, то получаем ошибку. Впрочем, раньше эта страница весила 18 Мб или больше, и дождаться ее загрузки по Edge соединению где-нибудь в Индии было просто невозможно.


                                              У остальных все приблизительно так же. Если не находишься на быстром соединении (как минимум 3G/4G), то любым из современных сайтов пользоваться почти невозможно, включая даже не особо динамические GT и Habrahabr — почему-то часто начало страницы начинает отображаться, а затем браузер заменяет ее на страницу о невозможности загрузки. Как им это удается, не понимаю....


                                              Даже если SPA загрузилось, то оно не работает на медленном соединении. Кнопки не нажимаются, формы не отправляются, возникают ошибки одна загадочнее другой.

                                                0
                                                То что никто этого не делает — это другой вопрос. Технических проблем делать все по уму не существует. Нет никакой супер веской причины ждать загрузки JS бандла, чтобы изоморфное приложение работало, кроме лени разработчиков.
                                                  0

                                                  Ну что поделать, мир несовершенен. Любая технология, которую можно использовать неправильно, будет использована неправильно.


                                                  Из этой же серии приложения на Electron. Простой чатик будет выжирать 2- Гб памяти, и процентов 20-40 CPU. Если же необходимо несколько таких приложений, то к ноутбуку надо будет прилагать батарею Tesla и промышленный вентилятор для охлаждения. Но поток SPA и Electron-приложений это не останавливает, к сожалению.


                                                  У разработчиков SPA нет никаких мотивов, чтобы делать небольшие приложения с поддержкой progressive enhancement. Люди, как я, которые много времени проводят в местах с плохим интернетом, не приносят их бизнесу достаточно прибыли, чтобы оправдать правильное написание/оптимизацию.

                                                    0
                                                    Можно использовать неправильно и использовать неправильно это все же разные вещи.

                                                    В своем комментарии, вы безапелляционно написали, что мол пока не загрузится бандл ничего сделать будет нельзя. Я в свою очередь написал, что это сильно зависит от разработчиков приложения и сама по себе такая возможность есть. И кстати говоря, нужно приложить не так уж и много усилий для того, чтобы обеспечить должный уровень progressive enhancement. Проверено на собственном опыте.

                                                    Основная проблема как раз и кроется в статьях/комментариях/итп где люди пишут как это сложно и не нужно, при этом мало кто действительно пробовать сделать.

                                                    У разработчиков SPA нет никаких мотивов, чтобы делать небольшие приложения с поддержкой progressive enhancement.

                                                    У SPA да, но изоформные приложения это не SPA в классическом смысле. У разработчиком таких приложений намного больше мотивов, ибо если ты уже научил свой сервер отвечать на все GET запросы изоморфно, то почему бы не научить его обрабатывать и POST запросты также? Нипочему.
                                    0

                                    Спасибо за очередной интересный доклад. Вот только я что-то так и не понял зачем нужен отдельный frontend-сервер, это же было до SPA, что сервер сам генерил HTML и отдавал в браузер, теперь получается тоже самое, только с посредником? Или я что-то не так понимаю?

                                      0

                                      Вся соль в том, что frontend-сервер и frontend-клиент теперь могут иметь 90+% общего кода.

                                        0

                                        А зачем нужен отдельный frontend-сервер, в чем его необходимость в данной архитектуре?

                                          0

                                          Классические SPA не дружат с поисковиками и индексированием (есть костыли, но они плохие). А изоморфные, благодаря серверному рендерингу, дружат.

                                            0

                                            А еще серверный рендеринг ускоряет визуализацию контента в браузере. Т.е. сначала, скажем, отдается 50кб html+css. Пользователь их сразу видит и может уже читать страничку, тыкать по ссылкам и т.д… А только потом уже незаметно для пользователя подгружается тяжелый js-bundle.
                                            Это быстрее чем классические SPA, которые сначала показывают пустую страницу, потом тянут js-bundle целиком, и только потом пользователь что-то видит в браузере.

                                              0

                                              Классический рендеринг на сервере (не SPA) тоже дружит с поисковиками и с индексированием, загружается страница тоже может быть небольшого размера, но это считается прошлый век.

                                                0

                                                Ну идея про 90+% общего кода точно классная. В теории это легче и дешевле поддерживать будет. Особенно для крупных проектов.
                                                На практике неясно, пока всё только устаканивается. Проблем хватает.

                                                  +1
                                                  А зачем вообще клиентский код, если возвращаемся к серверному? Это ж все придумывали ради оффлайна и аякса. Теперь всё это не надо?
                                                    0
                                                    Теперь совмещают преимущества серверного и клиентского рендеринга избавляясь от их недостатков за счёт дублирования кода рендеринга. Ну или пытаются совмещать и избавляться, но плохо получается.
                                                    0
                                                    90% общего кода получается при более-менее сложной логики только при выделении отдельного фронтенд-сервера для серверного рендеринга и шлюза к бэкендам. 90% — рендеринг HTML/DOM по адресу, 10% кода на клиенте — обработка UI событий (кроме событий адресной строки с потерей контекста) и запросы к серверу, 10% кода на сервере — трансляция запросов клиента к бэкенду. Основная логика — на бэкендах.
                                                0
                                                1. Обеспечивает отдачу статики, включая тяжелые JS для SPA-функциональности
                                                2. Обеспечивает отдачу отрендеренного HTML по URL (первый заход, поисковые и прочие боты, ориентирующиеся на HTML)
                                                3. (опционально) Служит для SPA шлюзом и агрегатором бэкенда
                                            +3

                                            За эволюцией веб-сообщества можно наблюдать бесконечно. Вот примерная история:


                                            • сервер отдает статический хтмл и ресурсы, просто и идиоматично
                                            • появился PHP, а давайте возвращать хтмл динамически
                                            • Js/JQuery, зачем возвращать хтмл динамически, если можно не заставлять юзера ждать перезагрузку страницы
                                            • только js/SPA, см. п.1 (сервер отдает статический хтмл и ресурсы)
                                            • А давайте снова возвращать хтмл динамически
                                            • (будущее) непонятный баланс между мнгновенным открытием сайта и устанавливаемыми веб-приложениями
                                              0
                                              Сейчас работаю frontend-разработчиком на проекте с классической архитектурой, где весь HTML генерируется PHP-фреймворком, однако настроен в будущем это изменить, написав соотв. изоморфное приложение. Но так как времени на конкретно эту задачу никто не выделит, задался вопросом, возможно ли внедрение в HTML, генерируемый PHP, кусочков отрендеренных на JS? Чтобы осуществить постепенный переход к «светлому будущему». Есть у кого-либо подобный опыт, возможно есть статьи, описывающий такой подход?
                                                +2
                                                Возможно. Поднимаете сервер на JS, который рендерит то, что нужно, а в шаблонах PHP делаете (самый простой способ, если есть доступ к настройкам сервера и понимание последствий в плане безопасности прежде всего) что-то типа
                                                <?php require 'http://js-server/needed-url' ?>
                                                

                                                Для Symfony есть бандл, который делает это более секурно и гибко, может использовать как рендер-сервер, так и запускать node как обычный процесс.
                                                  +2
                                                  P.S. В своём проекте я пошёл по другому пути. Маршрут обычного запроса на показ страницы:
                                                  1. пользователь вводит адрес
                                                  2. браузер делает запрос на nginx
                                                  3. nginx проверяет есть ли статика, удовлетворяющая запросу
                                                  3.1. если есть — отдаёт
                                                  3.2. если нет — проксирует на js-сервер
                                                  4. js сервер проверяет может ли отрендерить результат сам (возможно обращаясь к php-серверу по API)
                                                  4.1. если есть — рендерит
                                                  4.2. если нет — проксирует на php-сервер
                                                  5. php-сервер обрабатывает запрос обычным путём
                                                    0
                                                    Значительно ли падает время ответа сервера на запрос при таком подходе?
                                                      +1
                                                      Незначительно. По сути на размер пингов от nginx до js и от js до php в случае если рендерить должен php (естественно в предположении, что у js ничего надолго поток не захватывает, всё асинхронно). На проде у нас это максимум два физических хоста в одной стойке, а часто и просто два процесса (докер-контейнера) на одной виртуалке.

                                                      Если критично, то можно роутинг сделать на nginx, чтобы он по урлу и прочим параметрам запроса определял куда направлять запросы на js или php. Но это поддерживать сложнее — нужно внедренные на JS-сервере фичи не забывать описывать в конфиге js.
                                                        0
                                                        Большое спасибо вам за советы!

                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                              Самое читаемое