{"id":19777331,"url":"https://github.com/catamphetamine/react-pages","last_synced_at":"2025-04-09T22:14:05.744Z","repository":{"id":41274891,"uuid":"48551728","full_name":"catamphetamine/react-pages","owner":"catamphetamine","description":"A complete solution for building a React/Redux application: routing, page preloading, (optional) server-side rendering, asynchronous HTTP requests, document metadata, etc.","archived":false,"fork":false,"pushed_at":"2024-06-07T15:11:19.000Z","size":3023,"stargazers_count":175,"open_issues_count":0,"forks_count":28,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-09T22:14:00.388Z","etag":null,"topics":["isomorphic","react","react-router","redux"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/catamphetamine.png","metadata":{"files":{"readme":"README-ADVANCED.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-24T17:30:23.000Z","updated_at":"2025-02-11T03:07:55.000Z","dependencies_parsed_at":"2023-11-26T04:24:32.118Z","dependency_job_id":"dd2d6112-3f9a-42ba-9bd2-6b3e45023cdb","html_url":"https://github.com/catamphetamine/react-pages","commit_stats":{"total_commits":1077,"total_committers":8,"mean_commits":134.625,"dds":0.4401114206128134,"last_synced_commit":"96ab11cd05cdb51b50edfe6bf5b93e1cee97c160"},"previous_names":["catamphetamine/react-isomorphic-render","catamphetamine/react-website"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catamphetamine%2Freact-pages","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catamphetamine%2Freact-pages/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catamphetamine%2Freact-pages/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catamphetamine%2Freact-pages/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/catamphetamine","download_url":"https://codeload.github.com/catamphetamine/react-pages/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119290,"owners_count":21050755,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["isomorphic","react","react-router","redux"],"created_at":"2024-11-12T05:24:28.848Z","updated_at":"2025-04-09T22:14:05.725Z","avatar_url":"https://github.com/catamphetamine.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"This section contains advanced topics. This means that the features described here are for those who have already spent some time with this library and therefore won't be overwhelmed and confused by the topics covered here.\n\n## CSRF protection\n\n[Cross-Site Request Forgery attacks](http://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html) are the kind of attacks when a legitimate user is tricked into navigating a malicious website which, upon loading, sends a forged HTTP request (GET, POST) to the legitimate website therefore performing an action on behalf of the legitimate user (because the \"remember me\" cookie is also sent along).\n\nHow can a legitimate website guard its users from such attacks? One solution is to ignore the \"remember me\" cookie and force reading its value from an HTTP header. Because CSRF attacks can't send custom headers (at least using bare HTML/Javascript, without exploiting Adobe Flash plugin bugs, etc), this renders such hacking attempts useless.\n\nTherefore the API should only read \"remember me\" token from an HTTP header. The client-side application will read \"remember me\" cookie value and send it as part of an HTTP header for each HTTP request. Alternatively \"remember me\" token can be stored in a web browser's `localStorage`.\n\nSo, **javascript is required** on the client side in order for this CSRF attacks protection to work (because only javascript can set HTTP headers). If a developer instead prefers to run a website for javascript-disabled users (like [Tor](https://www.deepdotweb.com/)) then the only way is to authenticate users in REST API endpoints by a \"remember me\" cookie rather than `Authorization` HTTP header. This will open the website users to various possible javascriptless CSRF attacks.\n\n## `onLoaded`\n\nWhen using `load` static property on a page component with `{ client: true }` option it's sometimes required to perform some actions (e.g. adjust the current URL) after those `load`ers finish (and after the browser navigates to the loaded page). While with regular `load`ers it could be done using `componentDidMount()` such an approach wouldn't work for `{ client: true }` `load`ers because they're executed after `componentDidMount()`. The solution is `onLoaded` static property which is called after all `load`ers finish on client side. `onLoaded` static property won't work when `codeSplit: true` setting is configured. \u003c!-- (could be implemented as some `onPageLoaded` route attribute) --\u003e\n\n```js\nimport { replaceLocation } from 'react-pages'\n\nfunction Page() {\n  return (\n    \u003csection\u003e\n      ...\n    \u003c/section\u003e\n  )\n}\n\nPage.onLoaded = ({ dispatch, useSelector, location }) =\u003e {\n  if (isAnIdURL(location.pathname)) {\n    dispatch(replaceLocation(replaceIdWithAnAlias(location, useSelector(state =\u003e state.userProfilePage.userProfile))))\n  }\n}\n```\n\n## Restricted routes\n\nIn most applications some routes are only accessible by a specific group of users. One may ask what route restriction mechanisms does this library provide. The answer is: you actually don't need them. For example, in my projects the `load` function itself serves as a guard by querying a REST API endpoint which performs user authentication internally and throws a \"403 Access Denied\" error if a user doesn't have the permission to view the page.\n\n\u003c!--\n## Cancelling previous action\n\nE.g. for an autocomplete component querying backend for matches it can be useful to be able to abort the previous search for matches when the user enters additional characters. In this case `Promise` cancellation feature can be employed which requires using `bluebird` `Promise` implementation being [configured](http://bluebirdjs.com/docs/api/cancellation.html) for `Promise` cancellation and passing `cancelPrevious: true` flag in an asynchronous Redux \"action\".\n\n```js\nfunction autocompleteMatch(inputValue) {\n  return {\n    promise: ({ http }) =\u003e http.get(`/search?query=${inputValue}`),\n    events: ['AUTOCOMPLETE_MATCH_PENDING', 'AUTOCOMPLETE_MATCH_SUCCESS', 'AUTOCOMPLETE_MATCH_ERROR'],\n    cancelPrevious: true\n  }\n}\n```\n\nGotcha: when relying on `bluebird` `Promise` cancellation don't use `async/await` syntax which is transpiled by Babel using [Facebook's `regenerator`](https://github.com/facebook/regenerator) (as of 2017) which doesn't use `Promise`s internally meaning that the following `async/await` rewrite won't actually cancel the previous action:\n\n```js\n// Action cancellation won't work\nfunction autocompleteMatch(inputValue) {\n  return {\n    promise: async (({ http })) =\u003e await http.get(`/search?query=${inputValue}`),\n    events: ['AUTOCOMPLETE_MATCH_PENDING', 'AUTOCOMPLETE_MATCH_SUCCESS', 'AUTOCOMPLETE_MATCH_ERROR'],\n    cancelPrevious: true\n  }\n}\n```\n--\u003e\n\n## Redux module event and property naming\n\nBy default it generates `\"_PENDING\"`, `\"_SUCCESS\"` and `\"_ERROR\"` Redux events along with the corresponding camelCase properties in Redux state. One can customize that by supplying custom `reduxEventNaming` and `reduxPropertyNaming` functions.\n\n#### react-pages.js\n\n```js\nimport reduxSettings from './react-pages-redux'\n\nexport default {\n  // All the settings as before\n\n  ...reduxSettings\n}\n```\n\n#### react-pages-redux.js\n\n```js\nimport { underscoredToCamelCase } from 'react-pages'\n\nexport default {\n  // When supplying `event` instead of `events`\n  // as part of an asynchronous Redux action\n  // this will generate `events` from `event`\n  // using this function.\n  reduxEventNaming: (event) =\u003e ([\n    `${event}_PENDING`,\n    `${event}_SUCCESS`,\n    `${event}_ERROR`\n  ]),\n\n  // When using \"redux module\" tool\n  // this function will generate a Redux state property name from an event name.\n  // By default it's: event `GET_USERS_ERROR` =\u003e state.`getUsersError`.\n  reduxPropertyNaming: underscoredToCamelCase\n}\n```\n\n#### redux/blogPost.js\n\n```js\nimport { ReduxModule, eventName } from 'react-pages'\nimport reduxSettings from './react-pages-redux'\n\nconst redux = new ReduxModule('BLOG_POST', reduxSettings)\n...\n```\n\nNotice the extraction of these two configuration parameters (`reduxEventNaming` and `reduxPropertyNaming`) into a separate file `react-pages-redux.js`: this is done to break circular dependency on `./react-pages.js` file because the `routes` parameter inside `./react-pages.js` is the `./routes.js` file which `import`s React page components which in turn `import` action creators which in turn would import `./react-pages.js` hence the circular (recursive) dependency (same goes for the `reducers` parameter inside `./react-pages.js`).\n\n## Locales\n\nOn client side there are a couple utility functions available for localization purposes.\n\n`getPreferredLocales()` function returns user's \"preferred locales\" (taken from `locale` cookie and `Accept-Language` HTTP header) if server-side rendering is enabled.\n\n```js\nimport { getPreferredLocales } from 'react-pages'\n\ngetPreferredLocales()\n// E.g. [\"ru\", \"ru-RU\", \"en-US\", \"en\"]\n```\n\nWhen server-side rendering is disabled then use `getPreferredLocale()` function which returns the preferred locale of user's web browser (taken from `navigator.language`).\n\n```js\nimport { getPreferredLocale } from 'react-pages'\n\ngetPreferredLocale()\n// E.g. \"ru-RU\", \"en\".\n```\n\nTo convert locale to language use `getLanguageFromLocale()` function.\n\n```js\nimport { getLanguageFromLocale } from 'react-pages'\n\ngetLanguageFromLocale('ru-RU')\n// Outputs: \"ru\".\n```\n\n## Serving assets and API\n\nIn the introductory part of the README \"static\" files (assets) are served by `webpack-dev-server` on `localhost:8080`. It's for local development only. For production these \"static\" files must be served by someone else, be it a dedicated proxy server like NginX or (recommended) a cloud-based solution like Amazon S3.\n\nAlso, a real-world website most likely has some kind of an API, which, again, could be either a dedicated API server (e.g. written in Golang), a simple Node.js application or a modern \"serverless\" API like [Amazon Lambda](https://aws.amazon.com/lambda) deployed using [`apex`](https://github.com/apex/apex) and hosted in the cloud.\n\n#### The old-school way\n\nThe old-school way is to set up a \"proxy server\" like [NginX](https://www.sep.com/sep-blog/2014/08/20/hosting-the-node-api-in-nginx-with-a-reverse-proxy/) dispatching all incoming HTTP requests: serving \"static\" files, redirecting to the API server for `/api` calls, etc.\n\n\u003cdetails\u003e\n  \u003csummary\u003eThe old-school way\u003c/summary\u003e\n\n```nginx\nserver {\n  # Web server listens on port 80\n  listen 80;\n\n  # Serving \"static\" files (assets)\n  location /assets/ {\n    root \"/filesystem/path/to/static/files\";\n  }\n\n  # By default everything goes to the page rendering service\n  location / {\n    proxy_pass http://localhost:3001;\n  }\n\n  # Redirect \"/api\" requests to API service\n  location /api {\n    rewrite ^/api/?(.*) /$1 break;\n    proxy_pass http://localhost:3000;\n  }\n}\n```\n\nA quick Node.js proxy server could also be made up for development purposes using [http-proxy](https://github.com/nodejitsu/node-http-proxy) library.\n\n```js\nconst path = require('path')\nconst express = require('express')\nconst httpProxy = require('http-proxy')\n\n// Use Express or Koa, for example\nconst app = express()\nconst proxy = httpProxy.createProxyServer({})\n\n// Serve static files\napp.use('/assets', express.static(path.join(__dirname, '../build')))\n\n// Proxy `/api` calls to the API service\napp.use('/api', function(request, response) {\n  proxy.web(request, response, { target: 'http://localhost:3001' })\n})\n\n// Proxy all other HTTP requests to webpage rendering service\napp.use(function(request, response) {\n  proxy.web(request, response, { target: 'http://localhost:3000' })\n})\n\n// Web server listens on port `80`\napp.listen(80)\n```\n\u003c/details\u003e\n\n#### The modern way\n\nThe modern way is not using any \"proxy servers\" at all. Instead everything is distributed and decentralized. Webpack-built assets are uploaded to the cloud (e.g. Amazon S3) and webpack configuration option `.output.publicPath` is set to something like `https://s3-ap-southeast-1.amazonaws.com/my-bucket/folder-1/` (your CDN URL) so now serving \"static\" files is not your job – your only job is to upload them to the cloud after Webpack build finishes. API is dealt with in a similar way: CORS headers are set up to allow querying directly from a web browser by an absolute URL and the API is either hosted as a standalone API server or run \"serverless\"ly, say, on Amazon Lambda, and is queried by an absolute URL, like `https://at9y1jpex0.execute-api.us-east-1.amazonaws.com/master/users/list`.\n\n## Internal `render()` function\n\nFor some advanced use cases (though most likely no one's using this) the internal `render()` function is exposed.\n\n```js\nimport { render } from 'react-pages/server'\nimport settings from './react-pages'\n\n// Returns a Promise.\n//\n// redirect - redirection URL (in case of an HTTP redirect).\n// cookies - a `Set` of HTTP cookies to be set (`response.setHeader('Set-Cookie', cookie)` for each of them).\n// status  - HTTP response status.\n// content - rendered HTML document (a Node.js \"Readable Stream\").\n//\nconst { redirect, cookies, status, content } = await render(\n  {\n    url: request.url,\n    origin: `${request.protocol}://${request.host}`,\n    headers: request.headers\n  },\n  settings,\n  serverSideConfiguration\n)\n```\n\nThe `await render()` function call can be wrapped in a `try/catch` block and for the `catch` block there's also the exported `renderError(error)` function.\n\n```js\nimport { renderError } from 'react-pages/server'\n\n// status  - HTTP response status.\n// content - rendered error (a string).\n// contentType - HTTP `Content-Type` header (either `text/html` or `text/plain`).\n//\nconst { status, content, contentType } = renderError(error)\n```\n\n## All `react-pages.js` settings\n\n```javascript\n{\n  // Routes element.\n  routes: require('./src/routes')\n\n  // (optional)\n  // Redux reducers (an object)\n  reducers: require('./src/redux/index')\n\n  // A React component.\n  //\n  // React page component (`children` property)\n  // is rendered inside this \"container\" component.\n  // (e.g. Redux `\u003cProvider/\u003e`,\n  //  `react-hot-loader@3`'s `\u003cAppContainer/\u003e`\n  //  and other \"context providers\")\n  //\n  // By default it just wraps everything with Redux `\u003cProvider/\u003e`:\n  //\n  // export default ({ store, children }) =\u003e (\n  //   \u003cProvider store={store}\u003e\n  //     {children}\n  //   \u003c/Provider\u003e\n  // )\n  //\n  rootComponent: require('./src/Container')\n\n  // Use this flag to enable \"code splitting\" mode.\n  // See `README-CODE-SPLITTING` for more info.\n  // https://gitlab.com/catamphetamine/react-pages/blob/master/README-CODE-SPLITTING.md\n  codeSplit: true/false\n\n  // When using `load`s in a client-side-only set up,\n  // `react-pages` can show a \"loading\" screen on initial page load.\n  //\n  // Also, when using `codeSplit` with `getComponent`,\n  // route components are loaded after the initial page render.\n  // To hide webpage content until all route components are resolved,\n  // one may show a \"loading\" screen.\n  //\n  // To activate \"show initial load\" feature, supply an `InitialLoadComponent`.\n  //\n  // Properties:\n  // * initial: true\n  // * show: boolean\n  // * hideAnimationDuration: number\n  //\n  InitialLoadComponent\n\n  // When supplying `InitialLoadComponent`, one should also specify `initialLoadShowDelay`:\n  // the delay before showing the `InitialLoadComponent`.\n  // This delay could be used to only show `InitialLoadComponent` for initial loads\n  // that aren't fast enough.\n  initialLoadShowDelay: 0\n\n  // When supplying `InitialLoadComponent`, one should also specify `initialLoadHideAnimationDuration`:\n  // the duration of the hide animation of `InitialLoadComponent`, if it has a hide animation.\n  initialLoadHideAnimationDuration: 160\n\n  // When using `react-hot-loader` one can pass `hot` as a configuration parameter\n  // instead of passing a custom `rootComponent` component just for enabling `react-hot-loader`.\n  // import { hot } from 'react-hot-loader'\n  hot: hot\n\n  // (optional)\n  // Default `\u003cmeta/\u003e` (applies to all pages).\n  meta: { ... }\n\n  // (optional)\n  // User can add custom Redux middleware\n  reduxMiddleware: [...]\n\n  // (optional)\n  // User can add custom Redux store enhancers\n  reduxStoreEnhancers: [...]\n\n  // (optional)\n  // Is called for errors happening during the initial page render\n  // (which means during Server-Side Rendering\n  //  and the initial client-side render `load`s).\n  //\n  // For example, Auth0 users may listen for\n  // JWT token expiration here and redirect to a login page.\n  //\n  // Or, if `load` throws an \"Unauthorized\" error\n  // then a redirect to \"/unauthorized\" page can be made here.\n  //\n  // `path` is `url` without `?...` parameters.\n  // `redirect()` redirects to a URL.\n  //\n  onLoadError: (error, { path, url, redirect, useSelector, server }) =\u003e {\n    redirect(`/error?url=${encodeURIComponent(url)}\u0026error=${error.status}`)\n  }\n\n  // (not used)\n  // Gets current user's locale.\n  // getLocale: (state) =\u003e state.user.profile \u0026\u0026 state.user.profile.locale || getCookie('locale') || getPreferredLocale() || 'en'\n\n  // (optional)\n  // `http` utility settings\n  http:\n  {\n    // (optional)\n    // When set to `true`, it will automatically find and convert all ISO date strings\n    // to `Date` objects in HTTP responses of `application/json` type.\n    //\n    // This is more of a legacy feature that was historically \"on\" by default.\n    // Looking at this feature now, I wouldn't advise enabling it because it could potentially\n    // lead to a bug when it accidentally mistakes a string for a date.\n    // For example, some user could write a comment with the comment content being an ISO date string.\n    // If, when fetching that comment from the server, the application automatically finds and converts\n    // the comment text from a string to a `Date` instance, it will likely lead to a bug\n    // when the application attempts to access any string-specific methods of such `Date` instance,\n    // resulting in a possible crash of the application.\n    //\n    findAndConvertIsoDateStringsToDateInstances: true\n\n    // (optional)\n    transformUrl: (url, { server: boolean }) =\u003e url\n    // Using `http.transformUrl(url)` configuration parameter\n    // one can transform shortcut URLs like `api://items/123`\n    // into longer ones like `https://my-api.cloud-provider.com/items/123`.\n\n    // (optional)\n    //\n    // Is called before the HTTP request is sent.\n    // Developers can set custom HTTP headers here\n    // or change the HTTP request `Content-Type`.\n    //\n    // * `request` is a `superagent` `request` that can be modified\n    //   (for example, to set an HTTP header: `request.set(headerName, headerValue)`).\n    // * `originalUrl` is the URL argument of the `http` utility call.\n    // * `url` is the `originalUrl` transformed by `http.transformUrl()`\n    //   (if no `http.transformUrl()` is configured then `url` is the same as the `originalUrl`).\n    //\n    onRequest: (request, { url, originalUrl, useSelector }) =\u003e {}\n\n    // (optional)\n    // Catches all HTTP errors that weren't thrown from `load()` functions.\n    onError: (error, { url, location, redirect, dispatch, useSelector }) =\u003e {\n      if (isSomeParticularError(error)) {\n        redirect('/some-particular-error')\n        // `return true` indicates that the error has been handled by the developer\n        // and it shouldn't be re-thrown as an \"Unhandled rejection\".\n        return true\n      }\n    }\n    //\n    // Is called when `http` calls either fail or return an error.\n    // Is not called for errors happening during the initial page render\n    // which means it can only be called as part of an HTTP call\n    // triggered by some user interaction in a web browser.\n    //\n    // For example, Auth0 users may listen for\n    // JWT token expiration here and redirect to a login page.\n    //\n    // `path` is `url` without `?...` parameters.\n    // `redirect()` redirects to a URL.\n\n    // (optional)\n    getErrorData: (error) =\u003e ({ ... })\n    //\n    // Parses a `superagent` `Error` instance\n    // into a plain JSON object for storing it in Redux state.\n    // The reason is that `Error` instance can't be part of Redux state\n    // because it's not a plain JSON object and therefore violates Redux philosophy.\n    //\n    // In case of an `application/json` HTTP response\n    // the `error` instance has `.data` JSON object property\n    // which carries the `application/json` HTTP response payload.\n    //\n    // By default `getErrorData` takes the `application/json` HTTP response payload\n    // and complements it with HTTP response `status` and `Error` `message`.\n\n    // (optional)\n    // (experimental: didn't test this function parameter but it's likely to work)\n    //\n    catch: async (error, retryCount, helpers) =\u003e {}\n    //\n    // Can optionally retry an HTTP request in case of an error\n    // (e.g. if an Auth0 access token expired and has to be refreshed).\n    // https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/\n    //\n    // If an error happens then the logic is (concept):\n    //\n    // httpRequest().then(..., (error) =\u003e {\n    //   return catch(error, 0, helpers).then(httpRequest).then(..., (error) =\u003e {\n    //     return catch(error, 1, helpers).then(httpRequest).then(..., (error) =\u003e {\n    //       ...\n    //     ))\n    //   ))\n    // ))\n    //\n    // Auth0 `catch` example:\n    //\n    // catch(error, retryCount, helpers) {\n    //   if (retryCount === 0) {\n    //     if (error.status === 401 \u0026\u0026 error.data \u0026\u0026 error.data.name === 'TokenExpiredError') {\n    //       return requestNewAccessToken(localStorage.refreshToken)\n    //     }\n    //   }\n    //   throw error\n    // }\n    //\n    // The `helpers` argument object holds:\n    //\n    // * `getCookie(name)` – a helper function which works both on client and server.\n    //   This function can be used to obtain a \"refresh token\" stored in a non-\"httpOnly\" cookie.\n    //\n    // * `store` – Redux store.\n    //\n    // * `http` – `http` utility.\n  }\n\n  // (optional)\n  authentication:\n  {\n    // (optional)\n    accessToken: ({ useSelector, path, url, getCookie }) =\u003e String\n    //\n    // If specified, this \"access token\" will always be set\n    // automatically in the \"Authorization\" HTTP header\n    // when using `http` utility inside Redux actions.\n    // \"Access tokens\" are supposed to be short-lived\n    // and their storage requirements are less strict\n    // than those for the \"refresh token\".\n    // For example, an \"access token\" may be stored\n    // in a regular non-\"httpOnly\" cookie.\n    // Since this method is run both on client and server\n    // the provided `getCookie(name)` function works in both cases.\n    //\n    // `helpers` object holds:\n    //\n    // * `store` - Redux store\n\n    // (optional)\n    header: 'Authorization'\n    // The HTTP header containing authentication token\n    // (e.g. \"Authorization: Bearer {token}\").\n    // Is \"Authorization\" by default.\n    // (some people requested this setting for\n    //  some projects using 'X-Authorization' header\n    //  due to the 'Authorization' header being blocked)\n  }\n\n  // (optional)\n  //\n  // If some \"base path\" should be prepended to all URLs,\n  // set it as a `basename` parameter.\n  //\n  // It can be used, for example, for hosting a website on GitHub pages\n  // where each website has a prefix.\n  // For example, for website `username.github.io/repo`, the `basename` would be `/repo`.\n  //\n  // A `basename` should not include a trailing slash.\n  //\n  basename: '/path'\n\n  // (optional)\n  // When supplying `event` instead of `events`\n  // as part of an asynchronous Redux action\n  // this will generate `events` from `event`\n  // using this function.\n  reduxEventNaming: (event) =\u003e ([\n    `${event}_PENDING`,\n    `${event}_SUCCESS`,\n    `${event}_ERROR`\n  ])\n\n  // (optional)\n  // When using \"redux module\" tool\n  // this function will generate a Redux state property name for an event name.\n  // E.g. event `GET_USERS_ERROR` =\u003e state.`getUsersError`.\n  reduxPropertyNaming(event) {\n    // Converts `CAPS_LOCK_UNDERSCORED_NAMES` to `camelCasedNames`\n    return event.split('_')\n      .map((word, i) =\u003e  {\n        let firstLetter = word.slice(0, 1)\n        if (i === 0) {\n          firstLetter = firstLetter.toLowerCase()\n        }\n        return firstLetter + word.slice(1).toLowerCase()\n      })\n      .join('')\n  }\n}\n```\n\n## All webpage rendering server options\n\n```javascript\n{\n  // Specify `secure: true` flag to use `https` protocol instead of `http`.\n  // secure: true\n\n  // This setting is for people using a proxy server\n  // to query their API by relative URLs\n  // using the `http` utility in Redux \"action creators\".\n  // The purpose of this setting is to prepend `host` and `port`\n  // to such relative API URLs on the server side when using the `http` utility.\n  // Specify `secure: true` flag to use `https` protocol instead of `http`.\n  // Note that using a proxy server is considered kinda outdated.\n  proxy:\n  {\n    host: '192.168.0.1',\n    port: 3000,\n    // secure: true\n  }\n\n  // `assets` parameter is introduced for the cases\n  // when the project is built with Webpack.\n  //\n  // The reason is that usually the output filenames\n  // in Webpack contain `[hash]`es or `[chunkhash]`es,\n  // and so when the project is built\n  // the assets have not their original filenames (like \"main.js\")\n  // but rather autogenerated filenames (like \"main-0ad5f7ec51a....js\"),\n  // so the corresponding `\u003cscript/\u003e` tags must not be constant\n  // and must instead be autogenerated each time the project is built.\n  //\n  // The `assets` parameter provides URLs of javascript and CSS files\n  // which will be inserted into the \u003chead/\u003e element of the resulting Html webpage\n  // (as \u003cscript src=\"...\"/\u003e and \u003clink rel=\"style\" href=\"...\"/\u003e)\n  //\n  // Also a website \"favicon\" URL, if any.\n  //\n  // Can be an `object` or a `function` returning an `object`.\n  //\n  // `javascript` and `styles` can be `string`s or `object`s.\n  // If they are objects then one should also provide an `entry` parameter.\n  // If \"common\" entry is configured in Webpack\n  // then it's always included on every page.\n  //\n  assets: (path, { store }) =\u003e\n  {\n    return {\n      // Webpack \"entry points\" to be included\n      // on a page for this URL `path`.\n      // Defaults to `[\"main\"]`:\n      // If no \"entry points\" are configured in Webpack configuration\n      // then Webpack creates a single \"main\" entry point.\n      // entries: [...],\n\n      // Javascripts for the `entries`.\n      javascript: {\n        main: '/assets/main.js'\n      },\n\n      // (optional)\n      // Styles for the `entries`.\n      styles: {\n        main: '/assets/main.css'\n      },\n\n      // (optional)\n      // Website \"favicon\" URL.\n      icon: '/assets/icon.png'\n    }\n  },\n\n  // (optional)\n  // HTML code injection\n  html:\n  {\n    // (optional)\n    // Markup inserted into server rendered webpage's \u003chead/\u003e.\n    // Can be either a function returning a string or just a string.\n    head: (path, { store }) =\u003e String\n\n    // (optional)\n    // Markup inserted to the start of the server rendered webpage's \u003cbody/\u003e.\n    // Can be either a function returning a string or just a string.\n    bodyStart: (path, { store }) =\u003e String\n\n    // (optional)\n    // Markup inserted to the end of the server rendered webpage's \u003cbody/\u003e.\n    // Can be either a function returning a string or just a string.\n    bodyEnd: (path, { store }) =\u003e String\n  }\n\n  // (optional)\n  // When server-side rendering is enabled\n  // `Accept-Language` and `User-Agent` HTTP headers\n  // are  accessible inside this function.\n  // `locales` are parsed from the `Accept-Language` HTTP header.\n  getInitialState: ({ cookies, headers, locales }) =\u003e Object\n\n  // Is React Server Side Rendering enabled?\n  // (is `true` by default)\n  //\n  // (does not affect server side routing\n  //  and server side page loading)\n  //\n  // Can be used to offload React server-side rendering\n  // from the server side to the client's web browser\n  // (as a performance optimization) by setting it to `false`.\n  //\n  renderContent: `true`/`false`\n}\n```\n\n## All client side rendering options\n\n```javascript\n{\n  ...\n\n  // (optional)\n  // Is fired after a user performs navigation (and also on initial page load).\n  // Only on client side.\n  // This exists mainly for Google Analytics.\n  // `url` is a string (`path` + \"search\" (?...) + \"hash\" (#...)).\n  // \"search\" query parameters can be stripped in Google Analytics itself.\n  // They aren't stripped out-of-the-box because they might contain\n  // meaningful data like \"/search?query=dogs\".\n  // http://www.lunametrics.com/blog/2015/04/17/strip-query-parameters-google-analytics/\n  // The \"hash\" part should also be stripped manually inside `onNavigate` function\n  // because someone somewhere someday might make use of those \"hashes\".\n  onPageRendered: ({ url, location, params, dispatch, useSelector }) =\u003e {}\n\n  // (optional)\n  // Same as `onNavigate()` but fires when a user performs navigation (not after it).\n  // Only on client side.\n  onBeforeNavigate: ({ url, location, params, dispatch, useSelector }) =\u003e {}\n\n  // (optional)\n  // Is called as soon as Redux store is created.\n  //\n  // For example, client-side-only applications\n  // may capture this `store` as `window.store`\n  // to call `bindActionCreators()` for all actions (globally).\n  //\n  // onStoreCreated: store =\u003e window.store = store\n  //\n  // import { bindActionCreators } from 'redux'\n  // import actionCreators from './actions'\n  // const boundActionCreators = bindActionCreators(actionCreators, window.store.dispatch)\n  // export default boundActionCreators\n  //\n  // Not saying that this is even a \"good\" practice,\n  // more like \"legacy code\", but still my employer\n  // happened to have such binding, so I added this feature.\n  // Still this technique cuts down on a lot of redundant \"wiring\" code.\n  //\n  // Don't use `redirect`/`goto` \"pre-bound\" in such a way\n  // inside `load` because they won't work correctly there.\n  //\n  onStoreCreated: (store) =\u003e {}\n\n  // (optional)\n  // Configures Redux development tools.\n  //\n  // By default Redux development tools are enabled both in development (full-featured)\n  // and production (log only, for performance reasons) if the web browser extension is installed.\n  // The default behaviour is considered the best practice.\n  //\n  devtools:\n  {\n    // (optional)\n    // A developer can supply his custom `compose` function\n    // (e.g. when not using the web browser extension).\n    // By default, \"logOnlyInProduction\" compose function is used\n    // which is the best practice according to the web browser extension author:\n    // https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f\n    compose: import { composeWithDevToolsDevelopmentOnly } from '@redux-devtools/extension',\n\n    // (optional)\n    // Web browser extension options (when no custom `compose` is supplied).\n    // https://github.com/reduxjs/redux-devtools/tree/main\n    options: { ... }\n  }\n}\n```\n\nClient-side `render` function returns a `Promise` resolving to an object\n\n```js\n{\n  store,   // (Redux) store\n  rerender // Rerender React application (use it in development mode for hot reload)\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatamphetamine%2Freact-pages","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcatamphetamine%2Freact-pages","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatamphetamine%2Freact-pages/lists"}