Когда поднимается тема одностраничных приложений (SPA), мы склонны думать о браузерах, JavaScript, скорости и невидимости для поисковых систем. Это связано с тем, что SPA отображает содержимое страницы с помощью JavaScript, и поскольку сканеры не используют браузер для просмотра веб-страниц, они не могут просматривать и индексировать контент — или, по крайней мере, большинство из них не могут.
Это проблема, которую некоторые разработчики пытались решить различными способами:
- Добавление версии сайта с экранированным фрагментом, которая требует, чтобы все страницы были доступны в статической форме, и добавляет много дополнительной работы ( теперь не рекомендуется ).
- Использование платного сервиса для отмены просмотра SPA в статической разметке для поисковых роботов.
- Полагая, что поисковые системы теперь достаточно продвинуты, чтобы читать контент только для JavaScript. (Я бы не просто пока.)
Используя Node.js на сервере и React на клиенте, мы можем сделать наше приложение JavaScript универсальным (или изоморфным ). Это может предложить несколько преимуществ от рендеринга на стороне сервера и на стороне браузера, что позволит поисковым системам и людям, использующим браузеры, просматривать наш контент SPA.
В этом пошаговом руководстве я покажу, как создать универсальное приложение для блогов React, которое будет сначала отображать разметку на стороне сервера, чтобы сделать наш контент доступным для поисковых систем. Затем он позволит браузеру перейти в одностраничное приложение, которое будет быстрым и отзывчивым.
Начиная
В нашем универсальном блог-приложении будут использованы следующие технологии и инструменты:
- Node.js для управления пакетами и рендеринга на стороне сервера
- Реагировать на представления пользовательского интерфейса
- Express для простой серверной инфраструктуры JS
- React Router для маршрутизации
- React Hot Loader для горячей загрузки в разработке
- Поток для потока данных
- Cosmic JS для управления контентом
Для запуска выполните следующие команды:
mkdir react-universal-blog
cd react-universal-blog
Теперь создайте файл package.json
{
"name": "react-universal-blog",
"version": "1.0.0",
"engines": {
"node": "4.1.2",
"npm": "3.5.2"
},
"description": "",
"main": "app-server.js",
"dependencies": {
"babel-cli": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-es2017": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-register": "^6.26.0",
"cosmicjs": "^2.4.0",
"flux": "^3.1.3",
"history": "1.13.0",
"hogan-express": "^0.5.2",
"html-webpack-plugin": "^2.30.1",
"path": "^0.12.7",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-router": "1.0.1",
"webpack": "^3.5.6",
"webpack-dev-server": "^2.7.1"
},
"scripts": {
"webpack-dev-server": "NODE_ENV=development PORT=8080 webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback",
"development": "cp views/index.html public/index.html && NODE_ENV=development webpack && npm run webpack-dev-server"
},
"author": "",
"license": "ISC",
"devDependencies": {
"react-hot-loader": "^1.3.0"
}
}
В этом файле вы заметите, что мы добавили следующее:
- Babel, чтобы упаковать наши модули CommonJS и преобразовать наши ES6 и React JSX в совместимый с браузером JavaScript
- Официальный клиент Node.js для Cosmic JS, который легко обслуживает содержимое нашего блога с помощью размещаемого в облаке API содержимого Cosmic JS.
- Поток для управления данными приложения (что является очень важным элементом в нашем приложении React).
- Реагировать на управление пользовательским интерфейсом на сервере и в браузере
- React Router для маршрутов на сервере и в браузере
- Веб-пакет для объединения всего в файл
bundle.js
Мы также добавили скрипт в наш файл package.json
Когда мы запускаем npm run development
index.html
views
public
Затем он устанавливает базу контента для нашего webpack-dev-server
public/
.js
Наконец, это помогает нам отлаживать наши компоненты в источнике и дает запасной вариант для страниц, которые он не может найти (возвращается к index.html
Теперь давайте настроим наш файл конфигурации webpack, отредактировав файл webpack.config.js
// webpack.config.js
var webpack = require('webpack')
module.exports = {
devtool: 'eval',
entry: './app-client.js',
output: {
path: __dirname + '/public/dist',
filename: 'bundle.js',
publicPath: '/dist/'
},
module: {
loaders: [
{ test: /\.js$/, loaders: 'babel-loader', exclude: /node_modules/ },
{ test: /\.jsx$/, loaders: 'babel-loader', exclude: /node_modules/ }
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.COSMIC_BUCKET': JSON.stringify(process.env.COSMIC_BUCKET),
'process.env.COSMIC_READ_KEY': JSON.stringify(process.env.COSMIC_READ_KEY),
'process.env.COSMIC_WRITE_KEY': JSON.stringify(process.env.COSMIC_WRITE_KEY)
})
]
};
Вы заметите, что мы добавили свойство entry
app-client.js
Этот файл служит точкой входа нашего клиента приложения. Это означает, что с этого момента веб-пакет будет связывать наше приложение и выводить его в /public/dist/bundle.js
output
Мы также используем загрузчики, чтобы позволить Babel поработать над магией над нашими кодами ES6 и JSX. React Hot Loader используется для горячей загрузки (без обновления страницы!) Во время разработки.
Прежде чем мы перейдем к материалам, связанным с React, давайте подготовим внешний вид нашего блога. Так как я хотел бы, чтобы в этом уроке вы больше внимания уделяли функциональности, а не стилю, здесь мы будем использовать предварительно созданную внешнюю тему. Я выбрал один из Start Bootstrap под названием « Чистый блог» . В вашем терминале выполните следующие команды:
Создайте папку с именем views
index.html
Откройте файл HTML и добавьте следующий код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{{ site.title }}{{# page }} | {{ page.title }}{{/ page }}</title>
<!-- Bootstrap Core CSS -->
<link href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/css/clean-blog.min.css" rel="stylesheet">
<link href="/css/cosmic-custom.css" rel="stylesheet">
<!-- Custom Fonts -->
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body class="hidden">
<div id="app">{{{ reactMarkup }}}</div>
<script src="/js/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/clean-blog.min.js"></script>
<script src="/dist/bundle.js"></script>
</body>
</html>
Чтобы получить все файлы JS и CSS, включенные в public
репозитория GitHub . Нажмите здесь, чтобы загрузить файлы .
Обычно я бы использовал фантастический пакет React Bootstrap и воздерживался от использования jQuery. Однако, для краткости, мы сохраним предварительно встроенную функциональность темы jQuery.
В нашем файле index.html
div
id="app"
Переменная шаблона {{{ reactMarkup }}}
div
id="app"
Чтобы улучшить взаимодействие с пользователем, пока наш JavaScript загружает все, мы добавляем class="hidden"
Затем мы удаляем этот класс после монтирования React. Это может показаться немного сложным, но я покажу вам, как мы сделаем это через минуту.
На этом этапе ваше приложение должно иметь следующую структуру:
package.json
public
|-css
|-bootstrap.min.css
|-cosmic-custom.css
|-js
|-jquery.min.js
|-bootstrap.min.js
|-clean-blog.min.js
views
|-index.html
webpack.config.js
Теперь, когда у нас есть готовые статические элементы, давайте начнем создавать некоторые компоненты React.
Компоненты нашего блога (основной пример)
Давайте начнем создавать пользовательский интерфейс для нашего приложения, настроив страницы для нашего блога. Поскольку это будет портфолио для творческого профессионала, наш блог будет иметь следующие страницы:
- Дом
- Около
- Работа
- контакт
Давайте начнем с создания файла app-client.js
// app-client.js
import React from 'react'
import { render } from 'react-dom'
import { Router } from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'
const history = createBrowserHistory()
// Routes
import routes from './routes'
const Routes = (
<Router history={history}>
{ routes }
</Router>
)
const app = document.getElementById('app')
render(Routes, app)
Чтобы лучше понять, как работает React Router, вы можете посетить их репозиторий GitHub . Суть в том, что у нас есть в app-client.js
Router
Нашей разметке, отображаемой на сервере, не понадобится история браузера, поэтому мы создадим отдельный файл routes.js
Добавьте следующее в файл routes.js
// routes.js
import React, { Component } from 'react'
import { Route, IndexRoute, Link } from 'react-router'
// Main component
class App extends Component {
componentDidMount(){
document.body.className=''
}
render(){
return (
<div>
<h1>React Universal Blog</h1>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/work">Work</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
{ this.props.children }
</div>
)
}
}
// Pages
class Home extends Component {
render(){
return (
<div>
<h2>Home</h2>
<div>Some home page content</div>
</div>
)
}
}
class About extends Component {
render(){
return (
<div>
<h2>About</h2>
<div>Some about page content</div>
</div>
)
}
}
class Work extends Component {
render(){
return (
<div>
<h2>Work</h2>
<div>Some work page content</div>
</div>
)
}
}
class Contact extends Component {
render(){
return (
<div>
<h2>Contact</h2>
<div>Some contact page content</div>
</div>
)
}
}
class NoMatch extends Component {
render(){
return (
<div>
<h2>NoMatch</h2>
<div>404 error</div>
</div>
)
}
}
export default (
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="about" component={About}/>
<Route path="work" component={Work}/>
<Route path="contact" component={Contact}/>
<Route path="*" component={NoMatch}/>
</Route>
)
Отсюда у нас есть довольно простой рабочий пример блогового приложения с несколькими разными страницами. Теперь давайте запустим наше приложение и проверим его! В своем терминале выполните следующие команды:
mkdir public
npm install
npm run development
Затем перейдите по адресу http: // localhost: 8080 в своем браузере, чтобы увидеть ваш основной блог в действии.
После того, как все это сделано, настало время запустить его на сервере. Создайте файл app-server.js
// app-server.js
import React from 'react'
import { match, RoutingContext } from 'react-router'
import ReactDOMServer from 'react-dom/server'
import express from 'express'
import hogan from 'hogan-express'
// Routes
import routes from './routes'
// Express
const app = express()
app.engine('html', hogan)
app.set('views', __dirname + '/views')
app.use('/', express.static(__dirname + '/public/'))
app.set('port', (process.env.PORT || 3000))
app.get('*',(req, res) => {
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
const reactMarkup = ReactDOMServer.renderToStaticMarkup(<RoutingContext {...renderProps} />)
res.locals.reactMarkup = reactMarkup
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
// Success!
res.status(200).render('index.html')
} else {
res.status(404).render('index.html')
}
})
})
app.listen(app.get('port'))
console.info('==> Server is listening in ' + process.env.NODE_ENV + ' mode')
console.info('==> Go to http://localhost:%s', app.get('port'))
В app-server.js
Они преобразуют визуализированную разметку в строку и затем передают ее как переменную в наш шаблон.
Мы готовы запустить наш сервер и просмотреть наш код на нем, но сначала давайте создадим скрипт для этого.
Откройте файл package.json
script
// …
"scripts": {
"start": "npm run production",
"production": "rm -rf public/index.html && NODE_ENV=production webpack -p && NODE_ENV=production babel-node app-server.js --presets es2015",
"webpack-dev-server": "NODE_ENV=development PORT=8080 webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback",
"development": "cp views/index.html public/index.html && NODE_ENV=development webpack && npm run webpack-dev-server"
},
// …
Теперь, когда мы настроили наш production
В вашем терминале выполните:
npm start
Перейдите в браузере по адресу http: // localhost: 3000 . Вы должны увидеть свой простой контент блога и иметь возможность быстро и легко перемещаться по страницам в режиме SPA.
Идите вперед и нажмите view source
Обратите внимание, что наш SPA-код также доступен для всех роботов. Мы получаем лучшее из обоих миров!
Выводы
В этой первой части мы начали изучать мир React и увидим, как мы можем использовать его вместе с Node.js для создания универсального блогового приложения React.
Если вы хотите поднять свой блог на новый уровень и знать, как добавлять и редактировать контент, не забудьте прочитать вторую часть, « Создание универсального блогового приложения React: внедрение Flux ». Мы начнем с того, как легко масштабировать наше универсальное блог-приложение React, используя организационные концепции React и шаблон Flux .
Мы объединились с Open SourceCraft, чтобы предложить вам 6 профессиональных советов от разработчиков React . Для более открытого контента, проверьте Open SourceCraft .