Дополню ответ выше пояснениями и примерами
Сервер для любой страницы должен уметь присылать ответ как в виде готовой html так и в виде данных для генерации данного html на клиенте, соответственно понадобится шаблонизатор способный работать как на клиенте, так и на сервере.
Такой сайт спокойно смогут просматривать поисковики, а так же браузеры с отключенным javascript, а когда все норм (js работает) мы получили готовую страницу при первом запросе, а дальше работаем по принципу SPA для последующих переходов между страницами.
Второй важный момент, поисковики ищут внутренние страницы сайта переходя по ссылкам
<a href="/page2">Page 2</a>
. Соответственно наше SPA должен запускать свой роутинг перехватывая клики по ссылкам и основываясь на атрибуте href кликнутой ссылки
Третий момент, придется позаботится о historyAPI для удобства пользователя.
Ну и на последок, как я это все реализовал у себя на сайте
У меня есть пачка шаблонов описывающих содержимое страниц, они работают как на сервере так и на клиенте
Есть один базовый шаблон, задающий структуру html документа, он работает только на сервере.
По запросу определенной страницы, сервер собирает из шаблонов полноценный html и отдает его браузеру, вот так:
https://d-belyaev.ru/
Браузер ищет все теги a на странице и вешает на них обработчик click:
function render(node) {
var links = node.getElementsByTagName('a');
for(let i = links.length; i--;) {
links[i].addEventListener('click', doLinkClick);
}
}
function doLinkClick(event) {
var href = event.target.getAttribute('href');
if(!href.startsWith('/')) return true;
event.preventDefault();
router(href);
}
Роутер по данному href отправляет ajax запрос на сервер, дописывая к нему ?json
На что сервер уже отвечает по другому:
https://d-belyaev.ru/?json
А реализовано это все вот таким роутом на сервере:
function indexRoute(request) {
var pageData = {
template : 'page-index',
activeMenuIndex : 0
};
if(request.url.query === 'json') {
request.json(pageData);
} else {
request.html(baseTemplate(pageData));
}
}
Правда в нем у меня пока не хватает обработки для 304 статуса (страница в кэше браузера), так как руки пока не дошли