Authors:
Inna Ivashchuk
Volodymyr Vyshko
Volodymyr Vyshko
Inna Ivashchuk
Plan
- How does search crawler work?
- How to use SEO power
- Typical troubles with SEO in SPA
- Live demo about Server Side rendering
- Q&A session
How does search crawler work?
How do pages get into Search ?
This is Googlebot
How Googlebot works...
Crawler
Queue
Crawler
URL
Procesing
Index
Ranking
Render
Queue
Renderer
HTML
Rendered HTML
URLs
Good news
but not right away
How to use SEO power?
Tip 1
Content matters
Understandable and informative content
Nice UI/UX
Valid HTML with semantic elements
<html>
<head>
<title>My super HTML</title>
</head>
<body>
<header>
<nav>
<ul><li><a href="/home">Home</a></li></ul>
</nav>
<h1>Page title</h1>
</header>
<main>
<article>Nice article can be there</article>
<section>
<form><input type="email" name="email"></form>
</section>
</main>
<footer> </footer>
</body>
</html>
HTML <meta> Tags
<head>
<meta charset="UTF-8">
<meta name="description" content="Star Wars slides about SEO">
<meta name="keywords" content="HTML,CSS,SEO,JavaScript,Vader,Luke,R2D2">
<meta name="author" content="Master Yoda">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
The <meta> tag provides metadata about the HTML document. Metadata will not be displayed on the page, but will be machine parsable.
Meta elements are typically used to specify page description, keywords, author of the document, last modified, and other metadata.
The metadata can be used by browsers (how to display content or reload page), search engines (keywords), or other web services.
Readable and meaningful URLs only
Tranditional Urls
- starwars.com/traditional-url
- starwars.com/page#section
Tricking the browser with fragment identifiers
- starwars.com/#about
- starwars.com/#!about
Best practice? Use History API
- starwars.com/home
<a href="/home" onClick={doSomething}>Home</a>
Don't forget about semantic elements details
<h1>Characters in Star Wars</h1>
<p>
There is you can find the collection of the most powerful characters of Star Wars
</p>
<img src="/img/jedi.jpg" alt="Super powerful Jedi">
<img src="/img/sith.jpg" alt="Super powerful Sith Lord">
Tip 2
Give a content as quickly as possible
Take care about page loading
Check page load speed
Run Lighthouse in Chrome DevTools
Tip 3
Make your content stand out
Provide descriptive title
Every page should have unique description
Typical troubles with SEO in SPA
SPA without JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<title>StarWars | The Official Star Wars Website</title>
<link rel="stylesheet" href="https://static.disney.io/starwars.css" type="text/css">
</head>
<body>
<div id="content"></div>
<script src="https://static.disney.io/main.min.js"></script>
</body>
</html>
Main troubles in SPA
- One title for all pages
- One collection of meta tags
- Content is missed (text, images)
- URLs to sub-pages are not rendered
React SEO basics with Helmet
import React from 'react';
import { Helmet } from 'react-helmet';
class MainPage extends React.Component {
// ...
render() {
return (
<div>
// ...
<Helmet>
<title> unique, descriptive title </title>
<meta name="description" content="unique, helpful snippet" />
</Helmet>
</div>
)
}
}
export default MainPage;
Live demo
Server Side Rendering
Text
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router';
import { Helmet } from 'react-helmet';
import { ServerStyleSheet } from 'styled-components';
import App from './src/app';
Server imports
const sheet = new ServerStyleSheet();
const content = ReactDOMServer.renderToString(
sheet.collectStyles(
<StaticRouter location={req.url} context={{}}>
<App />
</StaticRouter>
)
);
const helmet = Helmet.renderStatic();
res.send(`
<html>
<header>
${helmet.title.toString()}
${helmet.meta.toString()}
${sheet.getStyleTags()}
</header>
<body>
<div id="root">
${content}
</div>
<script src="client_bundle.js"></script>
</body>
</html>
`)
Render application on server
Webpack configuration
const path = require('path');
module.exports = {
entry: './src/client.js',
output: {
filename: 'client_bundle.js',
path: path.resolve(__dirname,
'build/public'),
publicPath: '/build/public',
},
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
}]
},
}
const path = require('path');
const webpackNodeExternals =
require('webpack-node-externals');
module.exports = {
target: 'node',
entry: './server.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
publicPath: '/build',
},
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
}]
},
externals: [webpackNodeExternals()]
}
Client
Server
"webpack:server": "webpack --config webpack.server.js --watch",
"webpack:client": "webpack --config webpack.client.js --watch",
"webpack:start": "nodemon --watch build exec node build/bundle.js",
"dev": "npm-run-all --parallel webpack:*"
package.json
nodemon - is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
npm-run-all - a CLI tool to run multiple npm-scripts in parallel or sequential.
Dependencies
hydrate() - Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
ReactDOM.hydrate(element, container[, callback])
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them. In development mode, React warns about mismatches during hydration.
Render application on client
Results
Dynamic rendering
export const botUserAgents = [
'googlebot',
'google-structured-data-testing-tool',
'bingbot',
'linkedinbot',
'mediapartners-google',
];
isPrerenderedUA = userAgent.matches(botUserAgents)
isMobileUA = userAgent.matches(['mobile', 'android'])
if (!isPrerenderedUA) {
...
} else {
servePreRendered(isMobileUA)
}
app.get('/', function(req, res) {
console.log('User-Agent: ' + req.headers['user-agent']);
});
Get user-agent:
Render only for bot:
Redux
import { renderToString } from 'react-dom/server'
function handleRender(req, res) {
// Create a new Redux store instance
const store = createStore(counterApp)
// Render the component to a string
const html = renderToString(
<Provider store={store}>
<App />
</Provider>
)
// Grab the initial state from our Redux store
const preloadedState = store.getState()
// Send the rendered page back to the client
res.send(renderFullPage(html, preloadedState))
}
Handling the Request
Inject Initial Component HTML and State
renderFullPage(html, preloadedState) {
return `
<!doctype html>
<html>
<head>
<title>Redux Universal Example</title>
</head>
<body>
<div id="root">${html}</div>
<script>
// WARNING: See the following for security issues around embedding JSON in HTML:
// http://redux.js.org/recipes/ServerRendering.html#security-considerations
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(
/</g,
'\\u003c'
)}
</script>
<script src="/static/bundle.js"></script>
</body>
</html>
`
}
The Client Side
import React from 'react'
import { hydrate } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import counterApp from './reducers'
// Grab the state from a global variable injected into the server-generated HTML
const preloadedState = window.__PRELOADED_STATE__
// Allow the passed state to be garbage-collected
delete window.__PRELOADED_STATE__
// Create Redux store with initial state
const store = createStore(counterApp, preloadedState)
hydrate(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Things to keep in mind
- componentDidMount is not called on the server — this means that none of your data-fetching that you’re used to place there will not be called
- state generated on the server side will not be passed to the client application state
- to make router work you need to make sure that the proper URL is passed to the application when it’s rendered on the server
Whats next ?
Gatsby, next.js - Two of the most popular solutions that provide SSR for React
Demo project source code:
Thank You
Presentation can be found:
Q&A
May the SPA be SEO optimized
By Inna Ivashchuk
May the SPA be SEO optimized
SEO, crawlers, and SSR with Next
- 665