@siteone/ssr-server
SSR server for React Apollo applications
Contents
- [@siteone/ssr-server ](#siteonessr-server-npm-versionnpm-url-dependency-statusdaviddm-imagedaviddm-url)
Features (docs WIP)
- robust and flexible logging
- cache management endpoints
- caching backed by Redis and provided via middleware based on
apicache
extended to support multi-instance apps
Installation
$ npm install --save @siteone/ssr-server
Service routes
General
Each service route except ping is secured via HTTP Basic auth. Credentials are provided in configuration in section serviceCredentials
.
How to programmatically use/consume service endpoints
const request = require('request')
const credentials = 'admin:siteonejenejlepsi' // service credentials
const buff = new Buffer(data)
const authHash = buff.toString('base64') // authorization hash
const options = {
method: 'GET',
url: 'http://localhost:3000/__cache/index',
headers: {
Authorization: `Basic ${authHash}`,
},
}
request(options, function(error, response) {
if (error) throw new Error(error)
console.log(response.body) // Data from service endpoint
})
/__ping
Ping Responds with plain text string pong
. Useful for monitoring response time or whether service is operational.
/__stats
Metrics SSR server exposes endpoint containing various useful metrics for monitoring systems/dashboards.
Sample output:
{
"server": {
"status": "up",
"name": "my-website-on-ssr-server",
"version": "2.2.2",
"startedAt": 1579700306454,
"uptime": 693,
"env": "development",
"requests": {
"total": 1,
"lastMinute": 0,
"lastFiveMins": 0,
"lastFifteenMins": 0
}
},
"node": {
"version": "v12.14.1",
"memoryUsage": "82M",
"uptime": 693.779505301
},
"system": {
"loadavg": [5.06103515625, 7.853515625, 11.1982421875],
"freeMemory": "72M",
"hostname": "Viktor-MacBook-Pro.local"
}
}
Cache
SSR server provides endpoints to display cache index and invalidate both single route or whole cached data.
/__cache/index
Current cache index Sample output:
{
"all": [
"/produkty/sporici-ucet/$$appendKey=desktop__walkin__internal__staging",
"/platby-mobilem-a-hodinkami/$$appendKey=desktop__walkin__internal__staging"
],
"groups": {}
}
/__cache/clear
Clear whole cache Clears whole cache and returns new cache index.
Expected output if clearing was succesful:
{
"all": [],
"groups": {}
}
/__cache/index/:target
Remove single record from cache Please note, that target
must be URL encoded cache key. Ex: /__cache/clear/%2Fprodukty%2Fsporici-ucet%2F%24%24appendKey%3Ddesktop__walkin__internal__staging
. Endpoint response is new cache index.
Example output:
{
"all": [
"/platby-mobilem-a-hodinkami/$$appendKey=desktop__walkin__internal__staging"
],
"groups": {}
}
/__cache/performance
Get cache performance Returns cache performance metrics as JSON. Requires trackPerformance
option to be enabled. WARNING: super cool feature, but may cause memory overhead issues. Don't use in production.
Example output:
[
{
"lastCacheHit": "/test-slamic$$appendKey=unknown",
"lastCacheMiss": null,
"callCount": 1,
"hitCount": 1,
"missCount": 0,
"hitRate": 1,
"hitRateLast100": 1,
"hitRateLast1000": 1,
"hitRateLast10000": 1,
"hitRateLast100000": 1
}
]
Configuration
SSR server can be configured in many ways via configuration object.
How to structure config
Configuration object is large. Here's example how to compose it in readable way.
Create a config
folder next to server entry point. Create new file for each configured topic. Combine them via object spreading in config/index.js
.
Server
-
port
Number- Port number to attach to
- default
3000
-
host
: String- Bind to host
-
0.0.0.0
to be discoverable on network -
127.0.0.1
to local discovery only - default
0.0.0.0
config/server.js
export default {
// Server config
server: {
port: 3005, // Port
host: '0.0.0.0', // Host
},
}
Sentry
-
enabled
: Boolean- Is Sentry logging enabled
- default
false
-
dsn
: String- Sentry project DSN
- default value
undefined
sentry.js
export default {
sentry: {
enabled: false,
dsn: 'https://<key>@sentry.siteone.cz/<project>',
},
}
Cache
-
enabled
: Boolean- Is caching enabled
- default
false
-
redisClient
: optional- if provided cache is saved to redis instead of memory
- required when running multi instance applications
- initialized redis client
- default
undefined
-
defaultDuration
: String|Number- Should be either a number (in ms) or a string that can be parsed using ms
- default
1 hour
-
headerBlacklist
: Array- List of headers that should never be cached
- default
[]
-
statusCodes.exclude
: Array- List status codes to specifically exclude (e.g. [404, 403] cache all responses unless they had a 404 or 403 status)
- default
[]
-
statusCodes.include
: Array- List status codes to require (e.g. [200] caches ONLY responses with a success/200 code)
- default
[]
-
trackPerformance
: Boolean- enable/disable performance tracking.
- WARNING: may cause memory overhead issues. Don't use in production.
- default
false
-
headers
: Object- Header overwrites
- default
{}
// Server side rendering cache
export default {
ssrCache: {
debug: false, // If true, enables console output
defaultDuration: '1 hour', // Should be either a number (in ms) or a string, defaults to 1 hour
enabled: true, // If false, turns off caching globally (useful on dev)
redisClient: client,
headerBlacklist: [], // OPTIONAL: List of headers that should never be cached
statusCodes: {
exclude: [], // List status codes to specifically exclude (e.g. [404, 403] cache all responses unless they had a 404 or 403 status)
include: [200], // List status codes to require (e.g. [200] caches ONLY responses with a success/200 code)
},
trackPerformance: false, // Enable/disable performance tracking... WARNING: super cool feature, but may cause memory overhead issues
headers: {
// 'cache-control': 'no-cache', // Example of header overwrite
},
},
}
Service endpoints credentials
All service endpoints are secured via username and password and verified via HTTP Basic auth header. Set credentials to something secure.
-
user
: String- username
- default
admin
-
password
: String- username
- default
siteonejenejlepsi
export default {
serviceCredentials: {
user: 'admin',
password: 'siteonejenejlepsi',
}
}
Proxies (docs WIP)
export default {
proxies: [
// {
// method: 'all', // Specify method
// route: '/', // Route path, regex or express route format
// proxy: {}, // Config object passed to http-proxy-middleware
// },
],
}
Beforewares (docs WIP)
Custom express middlewares registered before main SSR middleware.
export default {
beforewares: [],
}
Custom routes (docs WIP)
export default {
routes: [
// {
// method: 'all', // Specify method
// route: '/', // Route path, regex or express route format
// handler: (req, res, next) => next(), // Express route handler or array of those
// },
],
}
Static files serving (docs WIP)
export default {
static: {
root: path.join(process.cwd(), 'public'), // Static files root
options: {
immutable: true, // Are files in static folder immutable
maxAge: ms('10 days'), // Cache max age
},
},
}
Logging (docs WIP)
export default {
logs: {
transports: [new winston.transports.Console()], // Winston transports
// Winston log formatter functions
format: winston.format.combine(
winston.format.colorize(),
winston.format.json()
),
meta: false, // OPTIONAL: control whether you want to log the meta data about the request (default to true)
msg: 'HTTP {{req.method}} {{req.url}}', // OPTIONAL: customize the default logging message. E.g. '{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}'
expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true
colorize: true, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red).
// ignoreRoute: (req, res) => false, // Function which allows ignoring some routes based on request response data
},
}
Redirects
SSR server accepts config value redirects
which is Array
of objects with shape {route: String, target: String, method: String, qsa: Boolean, status: Number}
.
route
The parameter route
is a string and is required. It can contain parameters like :id
, :year([0-9]{4})?
or :action(view|edit)
. It's basically just the same as a route you would pass to app.get()
.
target
The parameter target
is a string and is required. It can contain parameters like :id
, :year?
or :action(view)
, where a ?
marks an optional parameter and (someString)
is a default value.
The parameters get replaced by their respective counterparts in the route.
app.redirect("/a/:id([0-9]+)?", "/b/:id(1)");
app.redirect("/c/:action(view|delete)?", "/d/:action?");
/a -> /b/1
/a/100 -> /b/100
/c -> /d
/c/view -> /d/view
status
The parameter status
is an integer and is optional. It is a HTTP (redirection) status code. It defaults to 301.
method
The parameter method
is a string and is optional. It is a VERB as in express' router. It defaults to all
.
qsa
The parameter qsa
is a boolean and is optional. It defaults to true
. If set to true, the query string will be appended. By default, it will be discarded.
SSR config (docs WIP)
export default {
ssr: {
muiSupport: false, // Add styles collection for material ui
csrOnly: false, // Use only client side rendering
applicationConfig: {},
excludeRoutes: [/pagebuilder/ig, /page-preview/ig], // Routes (array of regexes to exclude from SSR)
useCsrOnError: true, // Use client side rendering when SSR fails (overriden by csr only)
apolloClientFactory: () => true, // Factory function which creates configured apollo client
ApplicationRoot: () => true, // React root component
configureStore: () => true, // Configure Redux store function
createInitialState: req => ({}), // OPTIONAL: Function which creates Redux initial state from request data
bodyExtras: '', // extra markup to put in body
headExtras: '', // extra markup to put in head
nojsWarning: 'Zdá se, že máte vypnutý javascript. Zapněte si jej, aby vám tento web fungoval správně.',
// HTML templates for all response type scenarios
templates: {
page: template, // General page where application can be rendered
unsupportedBrowser: opts =>
template({ ...opts, prerenderedMarkup: 'Unsupported browser' }), // Template when user is browsing with unsupported browser
notFound: opts =>
template({ ...opts, prerenderedMarkup: 'Page not found' }), // Template when page is not found
error: opts => template({ ...opts, prerenderedMarkup: 'Fatal error' }), // General error template
},
}
}
License
MIT © SiteOne, s.r.o.