В мире веб-разработки технологии постоянно эволюционируют, стремясь найти оптимальный баланс между производительностью, удобством пользователя и эффективностью разработки. Одной из ключевых технологий, прошедших значительный путь развития, является Server-Side Rendering (SSR) или рендеринг на стороне сервера. В этой эволюции фреймворк Angular сыграл значительную роль.
От статических страниц к динамическому контенту
Изначально веб состоял из статических HTML-страниц, где весь контент формировался на сервере. С ростом потребности в динамическом контенте появились серверные технологии вроде PHP, ASP, и JSP, позволяющие генерировать HTML на лету.
Эра клиентского рендеринга и подъем Angular
Революция в веб-разработке произошла с появлением мощных JavaScript-фреймворков, и Angular (изначально AngularJS) был одним из пионеров в этой области. Выпущенный Google в 2010 году, AngularJS предложил новый подход к созданию динамических веб-приложений с использованием клиентского рендеринга (CSR).
Angular (версия 2+), выпущенный в 2016 году, продолжил эту традицию, предоставив разработчикам мощный инструментарий для создания сложных одностраничных приложений (SPA). Это позволило создавать более интерактивные и отзывчивые приложения, но также принесло новые вызовы:
- Медленная начальная загрузка из-за большого размера JavaScript-бандла.
- Проблемы с SEO, так как поисковые роботы не всегда могли корректно индексировать динамический контент.
- Увеличение времени до первого значимого отображения (First Meaningful Paint).
Возрождение SSR и роль Angular Universal
Осознав ограничения чисто клиентского подхода, сообщество Angular обратилось к идее серверного рендеринга, но уже на новом уровне. Так появился Angular Universal — официальное SSR-решение для Angular.
Angular Universal позволяет рендерить приложения Angular на сервере, что решает ключевые проблемы CSR:
- Улучшает начальную загрузку страницы, отправляя предварительно отрендеренный HTML.
- Повышает SEO, предоставляя поисковым роботам полностью сформированный контент.
- Улучшает производительность на мобильных устройствах и в сетях с медленным соединением.
Практика
На словах все звучит круто, давайте попробуем это проверить.
Для начала надо посмотреть что отдает приложение без SSR. Для этого надо создать "пустой" проект.
Тут и далее, код можно вставлять целиком в консоль
# проверяем, что у нас установлен Angular CLI
npm install -g @angular/cli
# создаем наш демо проект
ng new no-ssr-demo
У нас могут попросить выбрать необходимые параметры, везде жмем enter
В консоле мы должны увидеть вот такой вывод
✔ Packages installed successfully.
Successfully initialized git.
Если все успешно, то переходим в проект и запускаем его
cd no-ssr-demo/
npm start
Должен получиться вот такой вывод
Но давайте посмотрим, что получает браузер при первом обращении к нашему сайту, для этого будем использовать curl
. Если вы из под винды, просто перейдите по адресу view-source:http://localhost:4200/
. Результат будет аналогичный
# Важно! ваш сайт может быть запушен на другом порту, если это так, просто замените 4200 на ваш порт
curl localhost:4200
Какой же мы получили вывод?
<!doctype html>
<html lang="en">
<head>
<script type="module" src="/@vite/client"></script>
<meta charset="utf-8">
<title>NoSsrDemo</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css"></head>
<body>
<app-root></app-root>
<script src="polyfills.js" type="module"></script><script src="main.js" type="module"></script></body>
</html>
А теперь перейдем в браузер и сравним HTML
Наш тег app-root
силами Angular превратился в прекрасного лебедя, которого, к сожалению, бездушный робот не увидит. Но давайте это исправим, для примера сделаем небольшой блог, который можно будет писать используя разметку markdown
Создание простого блога из Markdown с помощью Angular и SSR
Если вы следовали шагам выше, это можно пропустить
npm install -g @angular/cli
Создаем наш блог. На вопрос ? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? (y/N)
нажмите yes
или y
ng new my-blog --routing --style=scss
cd my-blog
после успешного вывода, необходимо поставить библиотеку marked
, которая будет парсить наш mardown
npm install marked
Первым делом необходимо создать отображаемые ресурсы и настроить сервер
// папка с нашими файлами .md
const articlesFolder = resolve(serverDistFolder, '../../../articles');
Ниже реализуем API
для их получения
server.get('/api/articles', async (req, res) => {
// тут получение всех статей
});
server.get('/api/articles/:name', (req, res) => {
const fileName = req.params.name + '.md';
const filePath = path.join(articlesFolder, fileName);
fs.readFile(filePath, 'utf8')
.then((data) => res.send(data))
.catch(() => res.status(404).send('Article not found'));
});
В корне проекта создайте папку articles
в которую положите 1-2 файла с расширением .md
После запуска команды npm run build && npm run serve:ssr:my-blog
сервер начнет работу (ТУТ ВНИМАТЕЛЬНО) После каждого исправления команду надо перезапускать, так что если хотим разрабатывать фронт и не писать каждый раз билд, то надо запустить ее и отдельно написать npm run start
, чтобы запросы не падали, надо включить server.use(cors());
(ну и библиотеку установить соотвествено)
С грязной работой покончено, теперь у нас есть сервер, который отдает содержимое одной конкретной статьи, для всех вернет небольшое содержимое (первые 150 символов) и название файла
Пойдем писать фронт! тут буду останавливаться только на интересных местах. Проект лежит вот тут https://github.com/alaev-dev/my-blog
Самое главное - нам обязательно нужен резолвер. Почему? ответ в SSR
- нам нужны данные еще до сборки компонента
export const routes: Routes = [
{
path: 'article/:slug',
component: ArticleComponent,
resolve: {
content: ArticleResolver,
},
},
];
ArticleComponent
@Component({
...
template: <div [innerHTML]="content()"></div>
})
export class ArticleComponent {
readonly #route = inject(ActivatedRoute);
content = toSignal(this.#route.data.pipe(map(({ content }) => content)));
}
И ради чего мы старались? Теперь страница выглядит вот так
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.