От автора: недавно мой друг-разработчик рассказал мне о новом проекте электронной коммерции для клиента. «Мне бы нравилось использовать приложение React.js, если бы не было всех этих проблем SEO». Я думал, что уже убедил его в своем предыдущем посте на Vue.js о том, что SEO с JS-фреймворками управляем.
Наверное, нет! Поэтому я начал с нуля и объяснил своему другу, как обращаться с SEO с помощью React SPA. Сегодня я отвечу ему в словах, используя Next js для создания crawler-friendly e-commerce SPA.
В этом уроке я:
Создам проект Next.js.
Использую привязки React / Redux.
Напишу и проведу рендер товаров.
Создам статические файлы для SEO.
Разверну мои статические файлы с помощью Netlify.
Прежде чем начать практиковать, давайте рассмотрим немного теории.
Что такое Next.js?
Вкратце, Next.js представляет собой облегченный фреймворк для статических и обработанных сервером приложений React.
Не путайте его с Nuxt, который является основой для приложений Universal Vue.js, которые сначала вдохновляют Next. Они имеют очень похожие цели.
К настоящему моменту вы должны хотя бы услышать о React, но для ясности мы определим его как компонентную библиотеку JavaScript для создания интерфейсов.
И что мы подразумеваем под универсальным JavaScript? Ну, это относится к приложениям, где JavaScript работает как на клиенте, так и на сервере. Это замечательно как для производительности в загрузке на первой странице, так и для SEO-целей, как мы увидим это ниже.
Далее также есть классный набор функций, включая автоматическое разделение кода, простую маршрутизацию на стороне клиента, среду разработки на базе webpack и любую реализацию сервера Node.js. Он также может использоваться как генератор статического сайта.
Неудивительно, что уже используют такие крупные компании, как Netflix, Ticketmaster и GitHub.
Работа с React.js SEO
Что случилось с SEO в React SPA? Как и многие фреймворки frontend, рендеринг выполняется динамически с помощью JavaScript. Боты поисковых систем после этого испытывают затруднения при сканировании асинхронного содержимого наших страниц, что приводит к снижению эффективности SEO.
Поисковая оптимизация в настоящее время является очень конкурентным полем, и небольшие ошибки могут стоить вашему онлайн-бизнесу большого количества трафика.
Давайте посмотрим, как мы можем это исправить!
Как проверить, правильно ли сканируется мой контент SPA?
Я предлагаю вам запустить Fetch as Google из Google Search Console на каждой ключевой странице вашего сайта.
Имя довольно понятно, но вы можете использовать этот инструмент, чтобы боты находили ваш контент. Он скажет вам, действительно ли он может получить доступ к странице, как она ее отображает и заблокирован ли какой-либо ресурс страницы (изображения или скрипты) для робота Googlebot.
Если вы обнаружите, что динамический рендеринг JS вызывает какие-либо препятствия для сканирования в поисковых системах, вы можете быстро действовать на нем.
Как проверить, что мой контент сканируется?
Как я уже узнал с Vue.js, есть несколько решений этой проблемы. В этом случае ответ будет получен от Next.js.
Все, что вам нужно определить, это правильный подход для ваших конкретных потребностей:
Серверный рендер. В настройке SSR вы переносите процесс рендеринга на сервер. То, что затем возвращается клиенту, полностью отображает представления HTML, ослабляя логику интерфейса. По этой причине этот подход отлично подходит для приложений, чувствительных к времени.
Создание статических файлов. Этот легкий процесс выполняет действие по загрузке всех ваших активов в статический HTML для использования сканерами. Он выполняется только для страниц, которые запрашиваются ботами, поэтому они не блокируются всем JavaScript, в противном случае (для обычных пользователей) все загружается, как обычно.
Сторонние инструменты, такие как Prerender SPA Plugin & Prerender.io, также выполняют тот же процесс, что и последний, с отличными результатами.
Для этой демонстрации я решил создать статические файлы, потому что для этого не требуется сервер, который напрямую соответствует логике JAMstack.
Чтобы узнать больше об этих подходах рендеринга, просмотрите этот подробный видеоурок. Это было сделано для Vue.js, но концепции применимы к React.js.
Урок по Next.js: Обработка SEO в React.js SPA
Предварительные условия
Snipcart учетная запись (навсегда бесплатная в тестовом режиме).
Основное понимание одностраничных приложений.
1. Создание структуры проекта
Начнем с нуля. Создайте новую папку, в которой вам нравится, и запустите следующие команды:
npm init npm install --save next npm install --save react npm install --save react-dom npm install --save redux npm install --save react-redux
Теперь, когда у меня есть необходимые зависимости, давайте напишем фактическую структуру файла.
root ├───components ├───lib └───pages
2. Создание мока архитектуры реального проекта с Redux
Я хочу сделать эту демонстрацию максимально реальной. Поэтому, хотя это будет немного надуманно для нашего прецедента, я решил использовать Redux с привязками React / Redux.
Позже я объявлю магазин с продуктами как начальное состояние, но не любые действия и редукторы. Это делается только для того, чтобы вы приблизились к реальной архитектуре.
Перейдите в папку pages и создайте файл _app.js, который является совершенно новым дополнением к Next – будет работать, только если вы используете версию 6 и выше.
Он позволяет переходы страниц, границы ошибок и многое другое. В моем случае я буду использовать его для написания нового формата приложения, в котором используется поставщик React / Redux, чтобы он добавлял хранилище Redux в мои компоненты.
Отказ от ответственности: эта архитектура очень вдохновлена этим Next с демонстрацией Redux.
Вот содержание моего файла:
import App, {Container} from 'next/app' import React from 'react' import withReduxStore from '../lib/with-redux-store' import { Provider } from 'react-redux' class MyApp extends App { render () { const {Component, pageProps, reduxStore} = this.props return ( <Container> <Provider store={reduxStore}> <div id="main"> <h1 className="title">Peaky Blinders’ Store</h1> <Component {...pageProps} /> <div> <p> SEO-friendly Next.js app with a <a href="https://snipcart.com/">Snipcart</a> powered store. <a href="https://github.com/snipcart/next-snipcart">[See the code]</a> <a href="https://snipcart.com/blog/react-seo-nextjs-tutorial">[Read full tutorial]</a> </p> </div> </div> </Provider> </Container> ) } } export default withReduxStore(MyApp)
Как вы можете видеть, я предоставляю хранилище своему Provider и ретранслирую текущие реквизиты страницы на текущий компонент.
Я не экспортирую компонент напрямую, но вместо этого вызываю withReduxStore с MyApp. Вы должны создать эту функцию, поскольку она не существует на данный момент.
Это определенно самая сложная функция. Для цели этого сообщения я не буду объяснять это полностью, так как он немного более продвинутый, и сложный раздел работает только в том случае, если вы должны использовать рендеринг на стороне сервера. Поскольку я буду генерировать статические файлы, все должно быть хорошо.
Итак, перейдите в папку /lib и создайте файл with-redux-store.js со следующим содержимым:
import App from 'next/app' import {initializeStore} from '../store' const isServer = typeof window === 'undefined' const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__' function getOrCreateStore(initialState) { // Always make a new store if server, otherwise state is shared between requests if (isServer) { return initializeStore(initialState) } // Store in global variable if client if (!window[__NEXT_REDUX_STORE__]) { window[__NEXT_REDUX_STORE__] = initializeStore(initialState) } return window[__NEXT_REDUX_STORE__] } export default (App) => { return class Redux extends React.Component { static async getInitialProps (appContext) { const reduxStore = getOrCreateStore() // Provide the store to getInitialProps of pages appContext.ctx.reduxStore = reduxStore let appProps = {} if (App.getInitialProps) { appProps = await App.getInitialProps(appContext) } return { ...appProps, initialReduxState: reduxStore.getState() } } constructor(props) { super(props) this.reduxStore = getOrCreateStore(props.initialReduxState) } render() { return <App {...this.props} reduxStore={this.reduxStore} /> } } }
В принципе, это проверяет, работает ли приложение на сервере или в браузере, а затем решает, следует ли обслуживать новый экземпляр хранилища Redux или текущий. Как только он будет определен, я передам его компоненту App в качестве опоры.
Это даст вам доступ к хранилищу в каждом компоненте верхнего уровня.
Импортируйте initializeStore, который является последней частью «контроля данных», необходимой перед тем, как перейти в продукты. Создайте файл store.js непосредственно в корневой папке.
import { createStore } from 'redux' export const actionTypes = {} const initialState = { products: [ { name: 'My first product', price: 50, description: 'I like turtles', image: 'url', id: 1 },{ name: 'My second product', price: 100, description: 'I like zonks', image: 'url', id: 2 },{ name: 'My third product', price: 150, description: 'I like dragons', image: 'url', id: 3 } ] } // REDUCERS export const reducer = (state = initialState, action) => { switch (action.type) { default: return state } } export function initializeStore (initialState = initialState) { return createStore(reducer, initialState) }
Как упоминалось ранее, магазин пустой. Он действительно просто создает исходное состояние, но не предоставляет ничего другого. Вы по-прежнему будете размещать продукты там, поскольку это дает реалистичное представление о способе доступа к данным в компонентах, которые я буду определять на следующем шаге.
3. Написание и рендеринг продуктов
Для этой демонстрации я хочу два разных компонента: product.js, которые будут отображать ссылку на каждый продукт, и product.js, который будет отображать данные каждого продукта.
Напишите каждый из них в папке components/.
products будут немного сложнее, так как им нужно получить доступ к хранилищу Redux, но они оба остаются простыми, поскольку они являются функциональными компонентами. Они только дают то, что им дают в качестве реквизита, поэтому они могут быть представлены исключительно как функция, не распространяя ничего.
Вот компонент продукта:
import Link from 'next/link' const ProductLink = (props) => ( <div className="product"> <Link as={`/product/${props.id}`} href={`/product?id=${props.id}`}> <a> <img src={props.image} alt={props.name} height='250' className="thumbnail"/> <p>{ props.description }</p> <p>{props.name}</p> </a> </Link> </div>) var Products = ({ products }) => ( <div> <div className="products"> { products.map(props => ( <ProductLink key={props.id} {...props}/> )) } </div> </div>) export default Products
И вот теперь product.js:
export default (props) => (f <div className="product"> <a className="product" href={props.url }> <img src={props.image} alt={props.name} className="thumbnail"/> <p>{props.name}</p> </a> <button className="snipcart-add-item" data-item-name={props.name} data-item-id={props.id} data-item-image={props.image} data-item-url='/' data-item-price={props.price}> Buy it for {props.price} $ </button> </div>)
Теперь, когда у вас есть эти два компонента, вам нужно использовать их в маршрутах, чтобы они могли визуализировать данные.
Для этого создайте два новых файла в папке pages, index.js и product.js. Первый из них выглядит следующим образом:
import React from 'react' import {connect} from 'react-redux' import Products from '../components/products' import Head from 'next/head' class Index extends React.Component { render () { return ( <div> <Head> <link href="/static/main.css" rel="stylesheet" /> <meta name="title" content="Peaky Blinder's e-commerce" /> <meta name="description" content='Find the best Peaky Blinders products online.' /> </Head> <Products {...this.props}/> </div> ) } } const mapStateToProps = (state) => ({products: state.products}) export default connect(mapStateToProps)(Index)
Что делает React / Redux, вместо того, чтобы напрямую обращаться к хранилищу Redux, он дает вам функцию connect которая дает вам способ сопоставить часть состояния с prop. Хотя это и не сделано здесь, оно также может дать вам способ сопоставления функции диспетчеризации с prop.
Это полностью изолирует логику от уровня представления. Это действительно интересный способ делать вещи и в большой степени влиять на функциональное программирование, так как вы можете легко разделить все зависимости компонента непосредственно в своих реквизитах.
С учетом этого принципа определите второй файл следующим образом:
import React from 'react' import {connect} from 'react-redux' import ProductComp from '../components/product' import Head from 'next/head' class Product extends React.Component { static getInitialProps = ({query}) => ({id: query.id}) getProduct = () => (this.props.products.filter(x => x.id == this.props.id)[0]) render = () => ( <div> <Head> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script> <script src="https://cdn.snipcart.com/scripts/2.0/snipcart.js" data-api-key="YjdiNWIyOTUtZTIyMy00MWMwLTkwNDUtMzI1M2M2NTgxYjE0" id="snipcart"></script> <link href="https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css" rel="stylesheet" type="text/css" /> <link href="/static/main.css" rel="stylesheet" /> <meta name="title" content={"Peaky Blinder's " + this.getProduct().name} /> <meta name="description" content={this.getProduct().description} /> </Head> <a href="/">go back to home</a> <ProductComp {...(this.getProduct())}/> </div> ); } const mapStateToProps = (state) => ({products: state.products}) export default connect(mapStateToProps)(Product)
Таким образом, компоненты остаются «чистыми», что означает, что они не обрабатывают логику фильтрации и логику выборки. Они просто получают данные и показывают их.
Я также добавил необходимые скрипты Snipcart. Головной компонент Next.js — простая оболочка. Таким образом, все, что помещается внутри, будет вложено внутри основного тега отображаемой страницы.
Здесь я также добавил некоторые метатеги. В этом случае я использовал «title» и «description», которые вы должны заполнить исследуемыми ключевыми словами. Даже если они не будут отображаться на странице, они помогут сканерам понять содержимое вашей страницы.
Для SEO также важно знать о meta name=»robots». Вы будете использовать его в больших проектах, где у вас есть внутренние страницы, доступные после входа в систему или любая другая страница, которую вы не хотите индексировать.
4. Создание статических файлов для React SEO
Поскольку страницы создаются динамически с использованием данных хранилища Redux, вам необходимо указать некоторые пути для Next, чтобы он знал, какие маршруты следует создавать при создании статических файлов.
Для этого создайте файл next.config.js непосредственно в папке маршрута:
module.exports = { exportPathMap: function () { return { '/': { page: '/' }, '/product/1': { page: '/product', query: { id: "1" } }, '/product/2': { page: '/product', query: { id: "2" } }, '/product/3': { page: '/product', query: { id: "3" } } } } }
Мне очень нравится, как просто обрабатывается. Не нужно подключаться к процессу сборки; простой JS-файл позволяет вам это сделать.
Конечно, это было всего три продукта с легкими идентификаторами для жесткого кода здесь. Но нетрудно было бы создать возвращаемый объект динамически с некоторым заданным форматом, так как это файл JavaScript, в котором вы можете использовать любую логику.
Прежде чем развертывать это для любого третьего лица, давайте сначала попробуем его локально. Добавьте следующий файл scripts в файл package.json:
"scripts": { "build": "next build", "export": "next export", "deploy": "next build && next export", "start": "next" }
Теперь запустите npm start в корневой папке вашего проекта. Это запустит Next как сервер, и вы сможете получить доступ ко всем по адресу http://localhost:3000.
Если вы хотите сгенерировать свои статические файлы, вы можете сделать это, используя npm run export. Это создаст папку, в которой вы найдете их. Если вы хотите разместить их локально для проверки вывода, вы можете сделать это быстро с помощью пакета npm, такого как serve.
5. Развертывание статических файлов
Теперь вы готовы развернуть веб-сайт. Давайте использовать Netlify. Во-первых, вам нужно запушить свой код на репозиторий Git, а затем перейти на панель инструментов Netlify. Там выберите создать новый сайт со следующей конфигурацией:
Стоит отметить, что создатели Next также имеют продукт развертывания под названием Now. Он позволяет развертывать статические ресурсы после сборки непосредственно с вашего терминала с помощью одной команды. Это действительно аккуратно, однако, поскольку я уже использую репозиторий Git для показа моего кода, я решил придерживаться Netlify.
Демо и репозиторий GitHub
См. демо-версию здесь. См. Репозиторий GitHub здесь.
Другие важные общие соображения SEO
Индексирование Mobile-first теперь является одним из основных факторов ранжирования. Настолько, что вам следует позаботиться о своем мобильном опыте, как на рабочем столе, если не больше!
Если вы все еще не осознаете важность связи HTTPS, вы должны сразу же изучить ее. Я размещаю эту демонстрацию на Netlify, которая предоставляет бесплатные SSL-сертификаты со всеми тарифами.
Чтобы продвигать свою SEO-игру, вы захотите создать отличный контент. Вы также хотите иметь возможность легко редактировать и оптимизировать его. Для целей редактирования контента подумайте о том, чтобы бросить одну из этих безголовых CMS в микс!
Не забудьте добавить соответствующие метатеги, как мы видели ранее. Здесь также очень важна карта сайта ваших страниц приложений. Вы можете найти отличный пример того, как создать карту сайта для проектов Next.js.
Заключительные мысли
Игра с Next.js и React была действительно забавной. Я не сталкивался с какими-либо серьезными проблемами, потому что все было прямо сказано в их документах, которые действительно тщательны. Мне нравится подход «викторины», который он имеет, оригинал!
Мне потребовалось около двух часов, чтобы построить все это. Потребовалось немного больше времени, используя привязку react-redux: я некоторое время блуждал в своих примерах, чтобы четко понять, что происходит.
Чтобы продвинуть все это дальше, было бы забавно объединить магазин с большим количеством продуктов, чтобы динамически генерировать pathMap. Я также задаюсь вопросом, в какой момент процесс сборки становится раздутым, если у вас слишком много маршрутов для экспорта. Если у вас есть ответ на этот вопрос, дайте мне знать в комментариях!
С творчеством, отличным кодированием и продуманной заботой о SEO ничего не стоит на пути ваших следующих проектов!
Автор: Maxime Laboissonniere
Источник: https://snipcart.com/
Редакция: Команда webformyself.