Одностраничные и многостраничные веб-приложения: плюсы, минусы, подводные камни. Одностраничные веб-приложения (SPA) с библиотекой KnockoutJS Одностраничные веб приложения

Одностраничное приложение или SPA — single page application — сайт или веб-приложение, в основе которого находится единственный HTML-документ. Обычно в подобном приложении на HTML-странице подключается JavaScript-фреймворки («каркасы» для разработки) тип AngularJS, BackboneJS, Ember.js и др. Эти фреймворки позволяют отображать на странице разное содержимое, в зависимости от действий пользователей и/или состояния URL страницы. Изменение состояния может происходить при нажатии на ссылки, href которых состоит из фрагмента URL начинающегося с символа «#» . Иногда с пары символов «#!», в случае поискового продвижения это сайта (в Яндексе).

Содержимое подобного сайта подгружается с сервера при помощи AJAX — асинхронного JavaScript и XML . Для реализации работы через AJAX нужна также реализация части приложения на серверной стороне. Обычно используются скриптовые языки. Для удобства работы и масштабирования системы выбирают REST (архитектура взаимодействия частей приложения).

Одностраничное приложение и JavaScript-фреймворки

Для построения одностраничных приложений используют различные фреймворки:

  • backbone.js (рус .)- легкий библиотека, написана автором CoffeeScript и используемая для разработки SPA-страниц с поддержкой REST архитектуры
  • ember.js (рус .)- тоже бесплатный JavaScript-фреймворк основанный на модули Модели-Представления-Контроллера (шаблон разработки — MVC)
  • angular.js (рус .) — фреймворк, MVC. Один из авторов продолжается заниматься фреймворком, работая в Google.
Одностраничный сайт html

Можно построить используя работу с селекторами по идентификатору и целевому селектору :target , CSS-свойства для управлением видимостью содержимого (display, visibility и margin). Шаблоны одностраничного сайта включает все необходимое содержимое для работы посетителя. В этом простейшем случае подключать JS-фреймворки обходимости нет.

Псевдокласс:target позволяет выбрать такие HTML-элементы на странице, атрибут id у которых совпадает со значением хеша из адресной строки. Например, если в адресной строке присутствует URI: http://сайт/test-po-html#result, то на HTML-странице будет найден элемент с идентификатором #result и к нему применятся CSS-стили.

Каркас такой страницы может выглядеть так (внимание! Для упрощения используется одинаковая высота у всех страниц. На практике объем содержимого буде разным)

Container{ font: 1em sans-serif; width:600px; min-height:500px; margin:auto; border:1px solid #000; position: relative; } h1,h2,h3,h4,h5,h6 {margin: 0;} .page{ width:600px; height:430px; position: absolute; top:60px; display: none; background-color: #fff; } div:first-of-type{ display:block; z-index: 1; } div:target{ display:block; z-index: 2; }

Одностраничный статичный сайт ссылка1 ссылка2 ссылка3 ссылка4 страница 1 одностраничного сайта страница 2 одностраничного сайта страница 3 одностраничного сайта страница 4 одностраничного сайта

Посмотреть пример работы . Со страницами, использующими JS-фреймворки работа строится другим способом.

май 21 , 2017

Одностраничные сайты или Single Page Applications (SPA) - это круто. Главный их профит в том, что SPA быстрее и отзывчивее на действия пользователей. Достигается это за счет переноса логики работы на клиентскую сторону и активного взаимодействия с сервером посредством ajax.

Бытует мнение, что SPA - это мощные приложения на ангуляре или реакте, ворочающие тоннами данных в какой-нибудь панели управления или в сложном сервисе. И в целом это так. Но я убежден, что есть смысл писать одностраничные приложения не только для таких сервисов, но и для обычных корпоративных сайтов-визиток.

Зачем это надо и как это сделать, приложив немного усилий? Об этом ниже. Поехали.

Итак, зачем это баловство?

Самое главное - это скорость работы.

Конечно, при разработке одностраничного сайта-визитки мы столкнемся с некоторыми проблемами:

  • 1. Как подступиться, с чего начать?
  • 2. Как разобраться с историей браузера, с History API?
  • 3. Какой фреймворк или библиотеку изучить: ангуляр, реакт? А мы ни одного не знаем...
  • 4. Как заставить поисковики индексировать одностраничный сайт?

Ответы на эти вопросы:

  • 1. Разберемся в этой же статье, на примере простого сайта
  • 2. Тоже расскажу ниже, это десяток строк кода
  • 3. Никакой, обойдемся нативным javascript-ом и jQuery в качестве помощника
  • 4. Про поисковики будет следующая статья из этой серии

Почему без ангуляра-реакта?
Конечно же, это очень нужные темы, рекомендую их изучать. Но для нашего примера они не понадобятся, нам достаточно обладать минимальными знаниями javascript-a.

Одностраничники - это тема не одной статьи. Это будет целый проект, серия статей минимум из трех штук. И затрагиваться в нем будут самые разные темы. Но уточню. Мы будем строить не сложную админку со взаимодействием с сервером посредством REST API (по крайней мере, в самом начале). В первых уроках наш одностраничный сайт будет обычной визиткой из десятка страниц. Но сайт будет работать без перезагрузки страниц и радовать наших пользователей скоростью и отзывчивостью интерфейса.

Идея сайта, как он устроен

Возьмем самый обычный корпоративный сайт: главная страница, раздел "О проекте", контакты и блог. В разделе "Блог" будет несколько ссылок на внутренние страницы. На каждой странице забьем какой-нибудь контент и вставим немного перекрестных ссылок.

На каждой странице сайта, как правило, есть повторяющийся контент. У нас это будет шапка и меню. И есть основное содержимое страницы, которое меняется. Мы сделаем так: загрузим страницу всего один раз, а потом кликая по ссылкам, будем динамически подгружать нужное содержимое аяксом. При этом мы будем менять заголовок страницы во вкладке браузера, url в адресной строке и запоминать историю в браузере, чтобы работала навигация через кнопки Назад/Вперед браузера.

Контент для каждой отдельной странице будет храниться в отдельном html-файле.

Можете сразу посмотреть, что у нас в итоге получится -

Структура сайта и подготовительные работы

Структура файлов-папок такова. В корне проекта файл index.php и.htaccess. Почему именно php, а не html, расскажу позже. В папке css лежат стили в файле main.css. В папке js - библиотека jquery.js и главный файл приложения main.js. В папке pages лежат html-файлы с содержимым сайта - на каждую страницу по одному файлу.

Готовим контент

Я сделаю демо-сайт на примере своего проекта webdevkin. Набор страниц будет таким:

  • — main - Главная
  • — about - О проекте
  • — blog - Блог
    • — shop - Интернет-магазины
    • — frontend - Фронтенд
    • — mysql - База данных MySql
    • — widgets - Встраиваемые виджеты
  • — simpple - Проект Simpple
  • — contacts - Контакты

Соответственно, в папке pages будут лежать 9 html-файлов. Всю разметку для контента найдете в . Для примера приведу содержимое только одного файла - simpple.html

Проект Simpple вырос из блога сайт. Идея проекта в том, чтобы делать простые и легко встраиваемые виджеты, помогающие взаимодействовать с посетителями Вашего сайта. Прямо сейчас уже есть виджет опросов, который легко создать и встроить на любой сайт.

Как видно, никаких head, body, html, script здесь нет - только разметка, относящаяся к конкретной странице.

index.php и.htaccess

Почему не index.html?
Дело в том, что на нашем сайте будет одна-единственная физическая страница - index. Но нас интересуют и такие адреса, как site.ru/about, site.ru/contacts и прочее. Но страниц about и contacts в корне нашего сайта нет. Папка pages с набором html-файлов - это не полноценные страницы, а просто куски html-кода, которые встраиваются внутрь общего каркаса.

Поэтому, чтобы при обращении к site.ru/about не посыпались 500, 403 и еще бог знает какие ошибки, мы должны все входящие запросы на сайт перенаправлять на index.php, который уже и будет эти запросы разруливать. Впрочем, пока что index.php у нас - это обычная html-разметка без единой строчки php-кода (но это только пока). А в.htaccess мы пропишем следующее. Возвращаться к нему придется редко.

RewriteEngine On Options +SymLinksIfOwnerMatch RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-l RewriteRule ^(.+)$ index.php?q=$1

В тему
Однажды я писал статью про Простой RESTful-сервис на нативном PHP . Там Вы найдете немного больше информации про такой способ перенаправления запросов.

html-код будет у нас очень простым. В head подключаются стили css/main.css. В подвале 2 js-файла: js/jquery.js и js/main.js. А в body будет следующее:

Сначала выводим меню. Дальше идет заголовок страницы. И ниже пустой div с id=content (не обращайте внимания на style="" - парсер когда-нибудь выбесит и я его заменю). В #content будут подгружаться динамически содержимое страниц из файлов pages/*.html. Пока ничего интересного.

Только обратите внимание на атрибуты data-menu и data-link="ajax" у ссылок. Они введены для того, чтобы отличать ссылки-навигации от обычных внешних ссылок, которые на нашем сайте тоже будут. data-link="ajax" означает, что при клике по этой ссылке мы перехватим стандартное поведение браузера и возьмем работу со ссылкой в свои руки. А data-menu означает, какой пункт главного меню будет выделен при клике на оную ссылку. Здесь data-menu дублируется с атрибутом href, но возможны и другие варианты. Например, когда мы залезем в раздел frontend блога, то мы укажем data-menu="blog".

2 примера для наглядности:

simpple.ru Главная страница simpple.ru Стили main.css

Быстро пролистаем и скопипастим самое скучное - файл css/main.css.

Body { position: relative; font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; font-size: 1em; font-weight: 400; color: #333; } a, a:visited { color: steelblue; } a:hover { color: navy; } .wrapper { width: 80%; margin: 0 10%; } .spa-title { font-size: 1.2em; text-align: center; } menu { margin-top: 2em; padding: 0; text-align: center; } menu a { display: inline-block; margin-right: 10px; padding: 5px 15px; text-decoration: none; } menu a:hover, menu a.active { background-color: steelblue; color: white; } .page-title { text-align: center; } ul li { list-style-type: circle; }

А вот теперь самое интересное - javascript-код, который превратит наш набор отдельных файлов в одностраничный сайт.

javascript. Общий код и конфиги

Зададим каркас js-кода.

Var app = (function() { var config = {}; var ui = {}; // Привязка событий function _bindHandlers() { // ... } // Инициализация приложения function init() { // ... _bindHandlers(); } // Возвращаем наружу return { init: init } })(); // Запуск приложения $(document).ready(app.init);

Мы имеем отдельный модуль app, который при загрузке страницы запускает свою функцию init. В ней навешиваем обработчики событий и выполняем еще некоторый код. Также видим 2 объекта: config и ui. В ui будут закэшированы все dom-элементы, которые понадобятся нам в работе.

Var ui = { $body: $("body"), $menu: $("#menu"), $pageTitle: $("#page-title"), $content: $("#content") };

$menu нам нужно, чтобы выделять отдельные пункты меню, $pageTitle будем менять динамически при переходе между страницами, а в $content будет подгружаться содержимое файлов pages/*.html

А вот config выглядит интереснее.

Var config = { siteTitle: "Webdevkin SPA", mainPage: "main", pages: { main: { title: "Главная", menu: "main" }, about: { title: "О проекте", menu: "about" }, blog: { title: "Блог Webdevkin-a", menu: "blog" }, simpple: { title: "Проект Simpple", menu: "simpple" }, contacts: { title: "Контакты", menu: "contacts" }, shop: { title: "Интернет-магазины", menu: "blog" }, frontend: { title: "Статьи о фронтенде", menu: "blog" }, mysql: { title: "База данных Mysql", menu: "blog" }, widgets: { title: "Встраиваемые javascipt-виджеты", menu: "blog" } } };

siteTitle: "Webdevkin SPA" - заголовок сайта, используется в нескольких местах.
mainPage: "main" - указываем стартовую страницу сайта, ту, которая откроется при заходе на site.ru. Сейчас это главная страница main, но Вам запросто может прийти в голову поставить стартовую, например, "О проекте" - about.

Самое важное во всем конфиге - это объект pages. Каждое поле объекта описывает одну страницу сайта. Сейчас нам понадобятся только 2 пункта: заголовок страницы и меню, к которому оная страница относится. Ключи объекта, то есть страницы, совпадают с названиями файлов в pages/*.html и атрибутами href во внутренних ссылках.

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

Подгрузка контента с помощью ajax. History API

Пойдем по порядку. Займемся функцией init, в которой есть привязывание нужных событий _bindHandlers

// Привязка событий function _bindHandlers() { ui.$body.on("click", "a", _navigate); window.onpopstate = _popState; }

В первой строке мы отлавливаем клики на внутренние ссылки a и отправляем их в функцию _navigate. Теперь мы окончательно убедились, что нужны отдельные атрибуты data-link="ajax" :-)

Далее window.onpopstate = _popState;
Из документации.
Событие popstate отсылается объекту window каждый раз, когда активная запись истории меняется между двумя записями истории для одного и того же документа.
Проще говоря, это срабатывание кнопок Назад/Вперед в браузере. Как мы видим, нативное поведение браузера тоже нужно перехватывать. Поэтому мы отдадим управление функции _popState.

Для лучшего восприятия приведу код сразу для обеих функций

// Клик по ссылке function _navigate(e) { e.stopPropagation(); e.preventDefault(); var page = $(e.target).attr("href"); _loadPage(page); history.pushState({page: page}, "", page); } // Кнопки Назад/Вперед function _popState(e) { var page = (e.state && e.state.page) || config.mainPage; _loadPage(page); }

При явном клике по ссылке _navigate мы останавливаем всплытие события клика и отменяем дефолтное поведение браузера (переход по ссылке). Затем мы определяем страницу, которую мы хотим загрузить (понимаем по атрибуту href), и вызываем новую функцию _loadPage. Она и сделает всю основную работу по загрузке контента, изменению заголовка и прочее-прочее. И в конце через history.pushState добавляем новую запись в истории браузера. Да, мы сами, явным образом создаем историю браузера. Тогда, когда считаем нужным. И сохраняем данные о загружаемой странице в объект {page: page}. Эти данные нам пригодятся в следующей функции _popState.

В _popState идея аналогична: ищем нужную страницу и запускаем ту же _loadPage.

Var page = (e.state && e.state.page) || config.mainPage;

e.state && e.state.page - вытаскивает нам страницу из объекта истории, которую мы предусмотрительно записали в _navigate. Если же объект e.state недоступен (например, когда мы первый раз зашли на сайт site.ru и еще не успели побродить по нему), то берем страницу, указанную главной в нашем конфиге - config.mainPage.
По совести говоря, функция history.pushState и тот факт, что в window.onpopstate доступен объект e.state с данными, записанными в pushState, - это все, что нам достаточно знать о History API. Для более любопытных товарищей, не сомневаюсь, что гугление поможет найти и другие хорошие способы работы с историей браузера.

Мы же глубокими изысканиями заниматься не будем, а напишем код главной функции _loadPage

// Загрузка контента по странице function _loadPage(page) { var url = "pages/" + page + ".html", pageTitle = config.pages.title, menu = config.pages.menu; $.get(url, function(html) { document.title = pageTitle + " | " + config.siteTitle; ui.$menu.find("a").removeClass("active"); ui.$menu.find("a").addClass("active"); ui.$pageTitle.html(pageTitle); ui.$content.html(html); }); }

Выглядит он вполне заурядно. Сначала формируем url, то есть путь, по которому мы загрузим html для страницы. Потом вытаскиваем из конфига заголовок страницы и пункт меню, подлежащий выделению. Далее получаем html-содержимое страницы через банальный jQuery.get и выполняем еще ряд нехитрых действий.

а) Обновляем заголовок страницы
б) В 2 строки выделяем нужный пункт меню
в) Меняем заголовок уже на самой странице, в html-коде
г) Загружаем собственно html-содержимое страницы, полученное из url

Почти все! Остался маленький момент. Если мы сейчас перейдем, например, на страницу site.ru/blog и обновим ее, то загрузится не блог, а пустая страница. Потому что при инициализации мы _loadPage не вызываем. Чтобы это исправить, нам нужно дописать пару строк в функцию init, которая в итоге будет выглядеть так

// Инициализация приложения function init() { var page = document.location.pathname.substr(1) || config.mainPage; _loadPage(page); _bindHandlers(); }

Вот теперь точно все!

Подведем итоги и напомним ссылки

Итак, мы написали простой, но вполне рабочий одностраничный сайт, а также немного узнали, как работает History API. Честно говоря, чувствую себя режиссером плохого фильма, когда 80% времени ведут к какой-то грандиозной развязке, а в конце все оказывается намного проще, чем ожидалось.

С одной стороны, почти весь код - это просто подготовка, обвязка, написание конфигов и прочих скучных вещей. А действительно полезный код, выполняющий непосредственную работу, занимает 2 десятка строк.

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

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

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

И небольшой опрос

В этой статье речь пойдет о Single Page Application (SPA). Будут рассмотрены плюсы и минусы web-приложения построенного по принципам одностраничного сайта (SPA)

Что такое SPA

Single Page Application - сокращенно SPA, в переводе на русский язык означает “Приложение одной страницы”. Другими словами SPA - это web-приложение, размещенное на одной web-странице, которая для обеспечения работы загружает весь необходимый код вместе с загрузкой самой страницы. Приложение такого типа появились сравнительно недавно, с началом эры HTML5 и SPA является типичным представителем приложений на HTML5.

Как мы знаем, HTML5 это нечто иное как HTML + CSS3 + JavaScript + [несколько новых тегов]. Таким образом, SPA - это приложения написанные на языке JavaScript. И, следовательно, немного перефразировав предыдущие определение получаем:

“SPA - это web-приложение, размещенное на одной странице, которая для обеспечения работы загружает все javascript-файлы (модули, виджиты, контролы и т.д.) , а также файлы CSS вместе с загрузкой самой страницы.”

Если приложение достаточно сложное и содержит богатый функционал, как например, система электронного документооборота, то количество файлов со скриптами может достигать нескольких сотен, а то и тысяч. А “…загрузка всех скриптов…” никоим образом не означает, что при загрузке сайта будут загружены сразу все сотни и тысячи файлов со скриптами. Для решения проблемы загрузки большого количества скриптов в SPA призван API под названием AMD . AMD реализует возможность загрузки скриптов по требованию. То есть, если для “главной станицы” одностраничного портала потребовалось 3 скрипта, они будут загружены стразу перед стартом программы. А если пользователь кликнул на другую страницу одностраничного портала, например, “О программе”, то принцип AMD загрузит модуль (скрипт + разметка) только перед тем как перейти на эту страницу.

Получается немного скомкано: “Одна страница.. другая станица, третья страница… одностраничный портал”. Расставим все точки над “Ё”. Страница сайта, на котором размещены все ссылки на все CSS, и ссылки на скрипты, необходимые для работы SPA мы назовем “Web-страница”. Файл с такой странице обычно называется “index.html” (в ASP.NET MVC может быть index.cshtml или index.vbhtml или даже index.aspx) А страницы, которые переключает пользователь внутри одностраничного портала назовем “модули”.

Давайте рассмотрим плюсы и минуты данного подхода. Зачем всё это нужно и почему SPA так популярен?

SPA: Плюсы

Первым плюсом стоит отметить тот факт, что приложения на SPA отлично работают на устройствах как стационарных, так и мобильных. “Большие” компьютеры, планшеты, смартфоны, и, в конце-концов, простые телефоны (некоторые) могут беспрепятственно работать с сайтами построенных по принципу SPA. Итак, первый “плюс” - работа на большом количестве устройств , а значит, создав одно приложение, вы получаете гораздо большую аудиторию пользователей нежели при использовании стандартного подхода.

Далее второй “плюс” - богатый пользовательский интерфейс , так называемый User Experience. Так как web-страница одна, построить богатый, насыщенный пользовательский интерфейс гораздо проще. Проще хранить информацию о сеансе, управлять состояниями представлений (views) и управлять анимацией (в некоторых случаях).

Третий “плюс” - SPA существенно (в разы) сокращает так называемые “хождения по кругу”, то есть загрузку одного и того же контента снова и снова . Если ваш портал (сайт) использует шаблон, то вместе с основным содержанием какой-либо страницы посетитель сайта обязательно загружает разметку шаблона. Да, кэширование данных на данном этапе развития WWW достигло высочайших результатов, но если нечего кэшировать, то и время, и ресурсы на это не тратятся.

SPA: Минусы

Если вы программируете на C#, то единственным минусом SPA является необходимость изучения JavaScript. Во всяком случае, других глобальных проблем мне выяснить не удалось.

Составляющие SPA

Принципы любого фреймворка (о них поговорим позже), который реализует парадигму SPA должны придерживаться следующих понятий и определений:

  • SPA поддерживает клиентскую навигации. Все “хождения” пользователя по модулям-страницам однозначно фиксируются в истории навигации, причем навигация при этом является “глубокой”, то есть если пользователь скопирует и откроет ссылку на внутреннюю модуль-страницу в другом браузере или окне, он попадет на соответствующую страницу.
  • SPA размещается на одной web-странице, значит всё необходимое для работы сайта (портала) скрипты и стили должны быть определены в одном месте проекта - на единственной web-странице.
  • SPA хранит постоянно состояние (важные переменные) работы клиента (клиентского скрипта) в кэше браузера или в Web Storage.
  • SPA загружает все скрипты требующиеся для старта приложения при инициализации web-страницы.
  • SPA постепенно подгружает модули по требованию.
Шаблоны SPA (SPA templates)

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

Существует большое количество базовых библиотек (фреймворк - от английского слова framework - “основа, структура, каркас”), которые реализуют принцип Single Page Application. Что дают эти фреймворки:

  • обеспечивают базовые принципы для SPA разработки, минимизируя трудозатраты на решение универсальных задач (смотри раздел “Составляющие SPA);
  • фреймворки созданы сообществом разработчиков, а значит используют опыт создания сайтов множества программистов;
  • фреймворки являются отправной точкой для создания структуры на основе Single Page Application.

Так как я уже много лет работаю на платформе NET, то я буду рассматривать шаблоны Single Page Application на основе ASP.NET. Давайте рассмотрим следующую сравнительную таблицу.

Сравнение возможностей шаблонов SPA

В таблице приведены наиболее распространённые шаблоны для как основа построения Single Page Application приложения. Обратите внимание, синим фоном выделены базовые кирпичики для построения полноценного фреймворка, таких как DurandalJS и HotTowel, которые выделены зеленым цветом.

Итак, следуя данным предоставленных в таблице вы можете создать Single Page Application приложение используя “голый” ASP.NET и KnockoutJS. Однако, реализацию работы с данными (DAL) вам придется писать самостоятельно, впрочем, как и управление навигацией и историей навигации в том числе.

В последнее время в работе специалистов Netpeak Agency много сайтов, использующих AJAX-технологии , а также сайтов написанных на JavaScript фреймворках. Без дополнительной оптимизации они не готовы к продвижению в поисковых системах. Поэтому я детально опишу, что такое SPA-сайты, как они работают, и как можно сделать Single Page Application SEO-Friendly.

Статья будет полезна SEO-специалистам и владельцам сайтов, которые хотят перейти на Single Page Application, но не решаются, потому что SPA могут «поссориться» с поисковыми системами.

Что такое SPA

SPA (Single Page Application ) — одностраничное JavaScript приложение, которое запускается и работает в браузере. В отличии от «традиционного» сайта, архитектура на SPA-сайтах построена так, что рендеринг страницы полностью происходит на стороне клиента, а не на стороне сервера.

В браузере пользователя запускается JavaScript-приложение, а все необходимое содержимое страниц динамически загружается с помощью AJAX. Навигация по сайту происходит без перезагрузки страниц. За счет такой архитектуры, SPA-сайты работают быстрее, чем «традиционные» сайты.

Рассмотрим детальнее, как происходит загрузка и рендеринг содержимого на SPA-сайтах:

  • Пользователь запрашивает HTML содержимое сайта.
  • В ответ приходит JavaScript-приложение.
  • Приложение определяет, на какой странице находится пользователь, и какое содержимое ему нужно отобразить.
  • С помощью AJAX пользователь получает необходимый контент: CSS, JS, HTML и текстовый контент.
  • JavaScript-приложение обрабатывает полученные данные и отображает их в браузере.
  • При навигации по сайту обновляется не вся страница, а только необходимое содержимое.
  • Плюсы SPA-сайтов:

    • высокая скорость работы;
    • быстрая разработка;
    • создание версий для разных платформ на основе готового кода (desktop и mobile приложения) .

    Минусы SPA-сайтов:

    • JavaScript не обрабатывается большинством поисковых систем;
    • SPA-сайты не работают без включенного JS в браузере;
    • их нельзя анализировать на предмет ошибок популярными программами и инструментами (например, Netpeak Spider).

    Популярные JavaScript фреймворки:

    • Angular ;
    • Meteor ;
    • React ;
    • Backbone ;
    • Ember ;
    • Vue ;
    • Polymer ;
    • Knockout .
    Какие поисковые системы индексируют SPA-сайты

    На сегодняшний день только поисковый робот Google умеет рендерить содержимое SPA-сайтов, так как использует для рендеринга инструменты на базе Chrome 41. ASK.com использует выдачу Google. Для остальных поисковых систем необходимо наличие контента в коде в формате HTML.


    Оптимизация индексации SPA-сайтов

    Роботы Google и Yandex могут проиндексировать Single Page Application, если структура сайта соответствует определенным правилам. При этом, для Яндекса необходимо обязательное наличие полной HTML-копии страницы.

    Для Google нужно использовать только правильный формат URL. Летом 2018 Google перестанет индексировать HTML-копии страниц , а будет использовать только рендеринг.

    (!) Нельзя запрещать индексирование JS и CSS файлов для поисковых роботов Google. Ограничив их индексацию, Google не сможет проиндексировать содержимое SPA-сайтов.

    Существует два способа «заставить» поисковых роботов индексировать AJAX-страницы:

  • Использовать «?_escaped_fragment_=» .
  • Отдавать роботу HTML-копии определяя его по user-agent .
  • Использование «?_escaped_fragment_=»

    Этот способ подразумевает генерацию HTML-копий страниц (снимков) по отдельному URL с использованием параметра «?_escaped_fragment_» .

    Использование URL c «#!»

    Если адреса AJAX страниц формируются с помощью «#» (хеш) , значит нужно заменить их на «#!» (хешбенг) . Например, с https://example.com/#url на https://example.com/#!url .

    Google просканирует содержимое по основному URL, а робот Яндекса, обнаружив в URL «#!», запросит снимок страницы. Он заменит «#!» на «?_escaped_fragment_=» и просканирует ее по адресу https://example.com/?_escaped_fragment_=url .

    Примеры адресов c «#!» и HTML-копий страниц:

    • https://example.com/home#!page → https://example.com/home?_escaped_fragment_=page ;
    • https://example.com/index/#!main → https://example.com/index/?_escaped_fragment_=main ;
    • https://example.com/#!home/index → https://example.com/?_escaped_fragment_=home/index .
    Использование «традиционных» URL

    Если на сайте используются «традиционные» URL (https://example.com/url), необходимо указать на всех страницах мета-тег:

    Google просканирует содержимое по основному URL, а робот Яндекса, обнаружив на странице мета-тег , запросит HTML-копию страницы. Также добавит в URL параметр «?_escaped_fragment_=» и просканирует ее по адресу https://example.com/url?_escaped_fragment_=

    Для популярных фреймворков есть готовые решения, которые заменяют «#!» на «традиционные» URL, например, HTML5 mode для Angular .

    (!) В HTML-копии документа мета-тег размещать не следует: в этом случае страница не будет проиндексирована.

    (!) На страницах HTML-копий canonical должен либо отсутствовать, либо вести сам на себя.

    Например, на странице https://example.com/home?_escaped_fragment_= должен быть указан следующий canonical:

    Отдавать HTML-копии страницы по основному URL

    Этот способ подходит для SPA-сайта с «традиционными» URL. В чем суть: поисковый робот, запрашивая страницу по основному URL, вместо динамической версии получает HTML-копию страницы.

    Определить поискового робота можно по User-Agent. К примеру, по списку роботов Яндекса .

    Рендеринг HTML-копий

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

    HTML-копии — это отрендеренные версии страниц SPA-сайта. Например, содержимое исходного кода страницы SPA-сайта выглядит так:

    Single Page Application

    А вот отрендеренная HTML-копия этой страницы:

    TITLE страницы СОДЕРЖИМОЕ СТРАНИЦЫ


    Для создания HTML-копий страниц можно:

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

    Рендеринг на своих серверах

    Подходит для крупных проектов. Для рендеринга HTML-копий на своих серверах можно использовать следующие инструменты:

    • PhantomJS ;
    • Angular Universal ;
    • Angular-SEO .
    Рендеринг на аутсорсе

    Подходит для небольших проектов. Для рендеринга HTML-копий на сторонних серверах можно использовать такие инструменты:

    • BromBone ;
    • Prerender.io ;
    • RenderJS.io .
    Изоморфный JavaScript

    Изоморфный JavaScript позволяет выполнять рендеринг страницы как на сервере, так и в браузере пользователя. Таким образом, при первой загрузке страницы пользователь получает обычную HTML-страницу и JavaScript-приложение. Дальнейшая навигация по сайту для пользователя будет динамической, в то время как поисковые системы каждый раз будут получать HTML-версию страницы.

    Такой подход к разработке сайта позволяет убить сразу двух зайцев: сайт получает все преимущества SPA-сайта и возможность продвижения в поисковых системах без дополнительных внедрений.

    Рендеринг страницы на SPA-сайтах происходит на стороне клиента, поэтому без дополнительных доработок код ответа несуществующей страницы будет 200 ОК.

    SPA-сайты должны корректно обрабатывать несуществующие страницы и отдавать в качестве заголовка страницы 404 ошибку.

    Настройка Google Analytics

    Стандартный код Universal Analytics выполняется при загрузке каждой новой страницы, а SPA-сайты подгружают контент динамически, «имитируя» переход между страницами. Для того, чтобы Google Analytics корректно обрабатывал переходы на страницах, необходимо сделать так, чтобы счетчик Universal Analytics активировался каждый раз, когда на сайте меняется URL.

    Настроить Google Analytics для SPA-сайтов можно, используя Tag Manager и триггер «History» или — передавая данные вручную.

    С помощью Tag Manager и триггера «History» Выводы

    SPA-сайты — это настоящее и будущее, поэтому не нужно бояться брать в работу такие проекты. Single Page Application можно «подружить» с поисковыми системами. Чтобы ваш SPA-сайт был дружелюбен как к SEO, так и к пользователям я рекомендую:

  • Использовать «традиционные» URL.
  • Использовать изоморфные приложения или рендеринг HTML-копий.
  • Настроить правильную отдачу заголовка «404 Not Found».
  • Настроить корректную работу Google Analytics.
  • Одностраничные приложения

    В этой и последующих статьях будет описано средство Web API , которое является относительно новым дополнением платформы ASP.NET и позволяет быстро и легко создавать веб-службы, предоставляющие API-интерфейс для HTTP-клиентов.

    Средство Web API основано на том же базисе, что и приложения ASP.NET MVC Framework, но не является частью инфраструктуры ASP.NET MVC Framework. Вместо этого в Microsoft взяли набор ключевых классов и связанных с ними характеристик из пространства имен System.Web.Mvc и продублировали его в пространстве имен System.Web.Http .

    Идея в том, что Web API - это часть главной платформы ASP.NET и может использоваться в других типах веб-приложений либо в качестве автономного механизма веб-служб. Одним из главных применений средства Web API считается создание одностраничных приложений (single-page application - SPA) путем комбинирования Web API с возможностями ASP.NET MVC Framework. Далее будет показано, что собой представляют SPA-приложения и как они работают.

    Упрощение создания веб-служб является неотъемлемой особенностью Web API. Оно представляет собой значительное улучшение по сравнению с другими технологиями построения веб-служб, которые компания Microsoft предлагала на протяжении последнего десятилетия. Мне нравится средство Web API, и вы должны использовать его в своих проектах, не в последнюю очередь потому, что оно отличается простотой и построено на базе того же самого проектного решения, что и ASP.NET MVC Framework.

    Термин одностраничное приложение (SPA) применяется довольно широко. Наиболее согласованным является его определение как веб-приложения, начальное содержимое которого доставляется в виде комбинации HTML-разметки и кода JavaScript, а последующие операции выполняются с участием веб-службы REST, доставляющей данные в формате JSON в ответ на запросы Ajax.

    Это отличается от того вида приложений, которые строились в примерах ранее, где результатами операций, выполняемых пользователем, были новые HTML-документы, генерируемые в ответ на синхронные HTTP-запросы. Такие приложения будут называться приложениями полного обмена (round-trip application - RTA) .

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

    В большинстве приложений приемы SPA и RTA смешиваются, при этом каждая крупная область функциональности приложения доставляется как SPA, а навигация между областями функциональности управляется с применением стандартных HTTP-запросов, которые создают новый HTML-документ.

    Пример одностраничного приложения

    Для целей этих статей в Visual Studio создан новый проект MVC по имени WebServices с использованием шаблона Empty (Пустой). В разделе Add folders and core references for (Добавить папки и основные ссылки для) были отмечены флажки MVC и Web API, как показано на рисунке ниже:

    Этот проект будет использоваться для создания обычного приложения ASP.NET MVC Framework, после чего с помощью Web API будет создана веб-служба. По готовности веб-службы приложение ASP.NET MVC Framework будет превращено в одностраничное приложение.

    Создание модели

    Приложение будет создавать и поддерживать набор заявок на бронирование помещений. Приложение планируется сохранять простым, чтобы можно было сосредоточиться на механике описываемого средства, поэтому заявки на бронирование будут состоять только из имени заказчика и местоположения помещения. В папку Models добавлен файл класса по имени Reservation.cs, содержимое которого показано в примере ниже:

    Namespace WebServices.Models { public class Reservation { public int ReservationId { get; set; } public string ClientName { get; set; } public string Location { get; set; } } }

    Планируется создать простую коллекцию объектов Reservation, хранящуюся в памяти, которая будет действовать в качестве хранилища данных. Нет необходимости заниматься установкой базы данных, однако нужна возможность выполнения операций CRUD над коллекцией объектов модели, что позволит продемонстрировать ряд важных аспектов Web API. В папку Models добавляется также файл класса по имени ReservationRepository.cs:

    Using System.Collections.Generic; using System.Linq; namespace WebServices.Models { public class ReservationRepository { private static ReservationRepository repo = new ReservationRepository(); public static ReservationRepository Current { get { return repo; } } private List data = new List { new Reservation { ReservationId = 1, ClientName = "Петр", Location = "Отель"}, new Reservation { ReservationId = 2, ClientName = "Вася", Location = "Библиотека"}, new Reservation { ReservationId = 3, ClientName = "Игорь", Location = "Столовая"}, }; public IEnumerable GetAll() { return data; } public Reservation Get(int id) { return data.Where(r => r.ReservationId == id).FirstOrDefault(); } public Reservation Add(Reservation item) { item.ReservationId = data.Count + 1; data.Add(item); return item; } public void Remove(int id) { Reservation item = Get(id); if (item != null) { data.Remove(item); } } public bool Update(Reservation item) { Reservation storedItem = Get(item.ReservationId); if (storedItem != null) { storedItem.ClientName = item.ClientName; storedItem.Location = item.Location; return true; } else { return false; } } } }

    В реальном проекте пришлось бы позаботиться о разрыве тесной связи между классами и ввести в приложение интерфейсы, а также обеспечить внедрение зависимостей. Однако в этой теме внимание уделяется только Web API и приложениям SPA, так что когда дело дойдет до стандартных приемов, будут предприняты некоторые упрощения.

    Класс хранилища имеет начальный список из трех объектов Reservation и определяет методы, которые позволяют просматривать, добавлять, удалять и обновлять коллекцию. Поскольку постоянство в хранилище отсутствует, любые изменения, вносимые в хранилище, теряются в результате останова и перезапуска приложения, но этот пример целиком сосредоточен на способе, которым содержимое может быть доставлено, а не на том, каким образом оно хранится на сервере. Для обеспечения определенной доли постоянства между запросами создается экземпляр класса ReservationRepository, который доступен через статическое свойство Current.

    Установка пакетов NuGet

    В этой и последующих статьях будут применяться три пакета NuGet: jQuery, Bootstrap и Knockout. Библиотеки jQuery и Bootstrap уже были описаны и использовались ранее. Knockout - это библиотека, которую в Microsoft приспособили для одностраничных приложений. Она была создана Стивом Сандерсоном. Несмотря на то что Стив работает в Microsoft, пакет Knockout доступен в виде открытого кода на веб-сайте библиотеки Knockout и получил широкое распространение. Позже будет показано, как функционирует Knockout, а пока нужно установить упомянутые выше пакеты.

    Выберите пункт меню Tools --> Library Package Manager --> Package Manager Console (Сервис --> Диспетчер библиотечных пакетов --> Консоль диспетчера пакетов), чтобы открыть окно командной строки NuGet, и введите следующие команды:

    Install-Package jquery -version 1.10.2 -projectname WebServices Install-Package bootstrap -version 3.0.0 -projectname WebServices Install-Package knockoutjs -version 3.0.0 -projectname WebServices

    Добавление контроллера

    В пример проекта добавляется контроллер по имени Home, определение которого можно видеть в примере:

    Using WebServices.Models; using System.Web.Mvc; namespace WebServices.Controllers { public class HomeController: Controller { ReservationRepository repository = ReservationRepository.Current; public ViewResult Index() { return View(repository.GetAll()); } public ActionResult Add(Reservation item) { if (ModelState.IsValid) { repository.Add(item); return RedirectToAction("Index"); } else return View("Index"); } public ActionResult Update(Reservation item) { if (ModelState.IsValid && repository.Update(item)) return RedirectToAction("Index"); else return View("Index"); } } }

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

    Добавление компоновки и представлений

    Чтобы сгенерировать содержимое для приложения, создается папка Views/Shared, в которую добавляется файл представления по имени _Layout.cshtml с содержимым, показанным в примере ниже:

    @ViewBag.Title @RenderSection("Scripts") @RenderSection("Body")

    В этой базовой компоновке предусмотрены элементы для файлов CSS библиотеки Bootstrap. В компоновке определены два раздела, Scripts и Body, которые будут использоваться для вставки содержимого внутрь компоновки. Следующий шаг заключается в создании представления верхнего уровня для приложения. Хотя далее будет создаваться обычное приложение ASP.NET MVC Framework, известно, что в конечном итоге должно быть построено одностраничное приложение.

    Трансформацию делать будет проще, если создать единственное представление, которое содержит всю HTML-разметку, требуемую для приложения, даже при условии, что результат первоначально выглядит несколько странно. В папку Views/Home добавляется файл представления по имени Index.cshtml, содержимое которого приведено в примере ниже:

    @using WebServices.Models @model IEnumerable @{ ViewBag.Title = "Заявки на бронирование"; } @section Scripts { } @section Body { @Html.Partial("Summary", Model) @Html.Partial("Editor", new Reservation()) }

    Модель представления для этого представления является перечислением объектов Reservation, и для обеспечения строительных блоков функциональности, которую будет видеть пользователь, создаются два частичных представления. Файл с первым частичным представлением называется Summary.cshtml. Этот файл создан в папке Views/Home:

    @model IEnumerable Все заказы

    IDИмяПомещение @foreach (var item in Model) { }
    @item.ReservationId @item.ClientName @item.Location @Html.ActionLink("Удалить", "Remove", new { id = item.ReservationId }, new { @class = "btn btn-xs btn-primary" })

    Модель представления для частичного представления - то же самое перечисление объектов Reservation, и оно используется для генерации стилизованной таблицы с помощью Bootstrap в виде элемента

    , который отображает значения свойств этих объектов. Вспомогательный метод Html.ActionLink() применяется для генерации ссылки, которая будет вызывать действие Remove контроллера Home; ссылка стилизована в виде кнопки с использованием Bootstrap.

    Еще одно частичное представление называется Editor.cshtml и также находится в папке Views/Home. Содержимое этого файла приведено в примере ниже. Частичное представление содержит форму, которая применяется для создания новых заявок на бронирование. Отправка формы приводит к вызову действия Add контроллера Home.

    @model WebServices.Models.Reservation Создать заказ @using (Html.BeginForm("Add", "Home")) { Имя клиента @Html.TextBoxFor(m => m.ClientName, new { @class = "form-control" }) Помещение @Html.TextBoxFor(m => m.Location, new { @class = "form-control" }) Сохранить }

    Установка стартового URL и тестирование приложения

    Последний подготовительный шаг связан с установкой местоположения, куда Visual Studio будет переходить при запуске приложения. Выберите пункт WebServices Properties (Свойства WebServices) в меню Project (Проект) среды Visual Studio, в открывшемся диалоговом окне перейдите на вкладку Web и отметьте переключатель Specific Page (Определенная страница) в категории Start Action (Начальное действие). Вводить какое-либо значение не нужно - достаточно только выбора переключателя.

    Чтобы протестировать приложение в его классической форме ASP.NET MVC Framework, выберите пункт Start Debugging (Запустить отладку) в меню Debug среды Visual Studio. Вы увидите (немного странную) компоновку в стиле "все в одном", которая предоставляет пользователю список текущих заявок на бронирование вместе с возможностью их создания и удаления:

    В следующей статье мы добавим средства Web API для нашего приложения.