{"id":19451137,"url":"https://github.com/thekashey/react-prerendered-component","last_synced_at":"2025-04-06T16:13:32.142Z","repository":{"id":57145525,"uuid":"142147579","full_name":"theKashey/react-prerendered-component","owner":"theKashey","description":"🤔Partial hydration and caching in a pre-suspense era","archived":false,"fork":false,"pushed_at":"2023-01-26T07:27:07.000Z","size":249,"stargazers_count":175,"open_issues_count":8,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-30T14:11:10.676Z","etag":null,"topics":["cache","html","react","ssr"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theKashey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-24T11:14:25.000Z","updated_at":"2025-03-22T21:32:06.000Z","dependencies_parsed_at":"2023-02-14T15:01:07.257Z","dependency_job_id":null,"html_url":"https://github.com/theKashey/react-prerendered-component","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Freact-prerendered-component","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Freact-prerendered-component/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Freact-prerendered-component/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Freact-prerendered-component/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theKashey","download_url":"https://codeload.github.com/theKashey/react-prerendered-component/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247509237,"owners_count":20950232,"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":["cache","html","react","ssr"],"created_at":"2024-11-10T16:40:24.027Z","updated_at":"2025-04-06T16:13:32.119Z","avatar_url":"https://github.com/theKashey.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003eReact Prerendered Component\u003c/h1\u003e\n  \u003cbr/\u003e\n  Partial Hydration and Component-Level Caching \n  \u003cbr/\u003e\n  \u003cbr/\u003e\n  \n  \u003ca href=\"https://www.npmjs.com/package/react-prerendered-component\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/react-prerendered-component.svg?style=flat-square\" /\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n## Idea\nIn short: dont try to __run__ js code, and produce a react tree matching pre-rendered one,\nbut __use__ pre-rendered html until js code will be ready to replace it. Make it live.\n\nWhat else could be done on HTML level? Caching, _templatization_, and other good things to 🚀, just in a 3kb*.\n\n#### Prerendered component\n\u003eRender something on server, and use it as HTML on the client\n\n- Server side render data \n  - call `thisIsServer` somewhere, to setup enviroment.\n  - React-prerendered-component `will leave trails`, wrapping each block with div with _known_ id.\n- Hydrate the client side \n  - React-prerendered-component will search for _known_ ids, and `read rendered HTML` back from a page.\n- You site is ready!\n  - React-prerendered-components are ready. They are rendering a pre-existing HTML you send from a server.\n- Once any component ready to be replaced - hydrate \n  - But not before. That's the point - partial hydration, step by step\n  \nBonus - you can store and restore component state.\n\nMore details - https://twitter.com/theKashey/status/1021739948536295424\n\n## Usage\n\n1. __Restore__ data from HTML\n```js\n\u003cPrerenderedComponent\n  // restore - access DIV and get \"counter\" from HTML\n  restore={(el) =\u003e this.setState({counter: +el.querySelector('i').innerHTML})}\n  // once we read anything - go live!\n  live={!!this.state.counter}\n\u003e\n  \u003cp\u003eAm I alive?\u003c/p\u003e\n  \u003ci\u003e{this.props.counter}\u003c/i\u003e\n\u003c/PrerenderedComponent\u003e\n```\n\nIs components HTML was not generated during SSR, and it would be not present in the page code - \ncomponent will go `live` automatically, unless `strict` prop is set.\n\n2. To do a __partial hydrating__\n\nYou may keep some pieces of your code as \"raw HTML\" until needed. This needed includes:\n- code splitting. You may decrease time to __First Meaningful Paint_ code splitting JS code needed by some blocks, keeping those blocks visible.\nLater, after js-chunk loaded, it will rehydrate existing HTML.\n- deferring content below the fold. The same code splitting, where loading if component might be triggered using interception observer.\n\nIf your code splitting library (not React.lazy) supports \"preload\" - you may use it to control code splitting\n```js\nconst AsyncLoadedComponent = loadable(() =\u003e import('./deferredComponent'));\nconst AsyncLoadedComponent = imported(() =\u003e import('./deferredComponent'));\n\n\u003cPrerenderedComponent\n  live={AsyncLoadedComponent.preload()} // once Promise resolved - component will go live  \n\u003e\n  \u003cAsyncLoadedComponent /\u003e\n\u003c/PrerenderedComponent\u003e\n```\n\n3. Restore state from JSON stored among.\n```js\n\u003cPrerenderedComponent\n  // restore - access DIV and get \"counter\" from HTML\n  restore={(_,state) =\u003e this.setState(state)}\n  store={ this.state }\n  // once we read anything - go live!\n  live={!!this.state.counter}  \n\u003e\n  \u003cp\u003eAm I alive?\u003c/p\u003e\n  \u003ci\u003e{this.props.counter}\u003c/i\u003e\n\u003c/PrerenderedComponent\u003e\n```\n\n### This is not SSR friendly unless....\nWrap your application with `PrerenderedControler`. This would provide context for all nested components, and \"scope\" _counters_ used to\nrepresent nodes.\n\nThis is more Server Side requirement.\n```js\nimport {PrerenderedControler} from 'react-prerendered-component';\n\nReactDOM.renderToString(\u003cPrerenderedControler\u003e\u003cApp /\u003e\u003c/PrerenderedControler\u003e);\n```\n\n\u003e !!!!\n\nWithout `PrerenderedControler` SSR will always produce an unique HTML, you will be not able to match on Client Side.\n\n\u003e !!!!\n\n## Client side-only components\nIt could be a case - some components should live only client-side, and completely skipped during SSR.\n```js\nimport {render} from 'react-dom';\nimport { ClientSideComponent } from \"react-prerendered-component\";\n\nconst controller = cacheControler(cache);\n\nconst result = render(\n  \u003cClientSideComponent\u003e\n     Will be rendered only on client\n  \u003c/ClientSideComponent\u003e\n);\n\nconst result = hydrate(\n  \u003cPrerenderedControler hydrated\u003e\n      \u003cClientSideComponent\u003e\n         Will be rendered only on client __AFTER__ hydrate\n      \u003c/ClientSideComponent\u003e\n  \u003c/PrerenderedControler\u003e\n);  \n```\n\n- There is the same component for `ServerSideComponent`s\n- There are _hoc_ version for both cases\n```js\nimport {clientSideComponent} from 'react-prerendered-component';\n\nexport default clientSideComponent(MyComponent);\n```\n\n## Safe SSR-friendly code splitting\nPartial rehydration could benefit not only SSR-enhanced applications, but provide a better experience for simple code splitting.\n\nIn the case of SSR it's quite important, and quite hard, to load all the used chunks before\ntriggering `hydrate` method, or some _unloaded_ parts would be replaced by \"Loaders\".\n\nPreloaded could help here, if your code-splitting library support `preloading`, __even__ if it does not support SSR.\n```js\nimport imported from 'react-imported-component';\nimport {PrerenderedComponent} from \"react-prerendered-component\";\n\nconst AsyncComponent = imported(() =\u003e import('./myComponent.js'));\n\n\u003cPrerenderedComponent\n  // component will \"go live\" when chunk loading would be done\n  live={AsyncComponent.preload()}\n\u003e\n  // until component is not \"live\" prerendered HTML code would be used\n  // that's why you need to `preload`\n  \u003cAsyncComponent/\u003e\n\u003c/PrerenderedComponent\u003e\n```\nYet again - it works with any library which could `preload`, which is literally any library except\n`React.lazy`.\n\n## Caching\nPrerendered component could also work as a component-level cache.\nComponent caching is completely safe, compatible with any React version, but - absolutely\nsynchronous, thus no Memcache or Redis are possible.\n \n```js\nimport {renderToString, renderToNodeStream} from 'react-dom/server';\nimport {\n  PrerenderedControler, \n  cacheControler, \n  CachedLocation, \n  cacheRenderedToString, \n  createCacheStream\n} from \"react-prerendered-component\";\n\nconst controller = cacheControler(cache);\n\nconst result = renderToString(\n  \u003cPrerenderedControler control={control}\u003e\n     \u003cCachedLocation cacheKey=\"the-key\"\u003e\n        any content\n     \u003c/CachedLocation\u003e\n  \u003c/PrerenderedControler\u003e\n)\n\n// DO NOT USE result! It contains some system information  \nresult === \u003cx-cachedRANDOM_SEED-store-1\u003eany content\u003c/x-cachedRANDOM_SEED-store-1\u003e\n\n// actual caching performed in another place\nconst theRealResult = cacheRenderedToString(result);\n\ntheRealResult  === \"any content\";\n\n\n// Better use streams\nrenderToNodeStream(\n  \u003cPrerenderedControler control={control}\u003e\n     \u003cCachedLocation cacheKey=\"the-key\"\u003e\n        any content\n     \u003c/CachedLocation\u003e\n  \u003c/PrerenderedControler\u003e\n)\n.pipe(createCacheStream(control)) // magic here\n.pipe(res)\n```\nStream API is completely _stream_ and would not delay Time-To-First-Byte\n\n- `PrerenderedControler` - top level controller for a cache. Requires `controler` to be set\n- `CachedLocation` - location to be cached. \n  - `cacheKey` - string - they key\n  - `ttl` - number - time to live\n  - `refresh` - boolean - flag to ignore cache\n  - `clientCache` - boolean - flag to enable cache on clientSide (disabled by default)\n  - `noChange` - boolean - disables cache at all\n  - `variables` - object - varibles to use in templatization\n  \n  - `as=span` - string, used only for client-side cache to define a `wrapper` tag\n  - `className` - string, used only for client-side cache\n  - `rehydrate` - boolean, used only for client-side cache, false values would keep content as a _dead_ html.\n- `Placeholder` - a template value\n  - `name` - a variable name\n- `WithPlaceholder` - renderprop version of `Placeholder`   \n- `NotCacheable` - mark location as non-cacheable, preventing memoization    \n- `cacheControler(cache)` - a cache controller factor, requires object with `cache` interface to work.\n  - cache interface is `{ get(key): string, set(key, ttl):void }`\n  - cache implimentation is NOT provided by this library.\n\n#### Placeholder and Templatization\nTo optimize rendering performance and reduce memory usage you might use cache _templates_:\n```js\nimport {CachedLocation, Placeholder, WidthPlaceholder} from 'react-prerendered-component';\n\nconst Component = () =\u003e (\n  \u003cCachedLocation key=\"myKey\" variabes={{name: \"GitHub\", secret: 42 }}\u003e\n    the \u003cPlaceholder name=\"name\"/\u003e`s secret is \u003cPlaceholder name=\"secret\"/\u003e\n    // it's easy to use placeholders in a plain HTML \n    \n    \u003cWithPlaceholder\u003e\n    { placeholder =\u003e (\n       \u003cimg src=\"./img.jpg\" alt={placeholder(\"name\") + placeholder(\"secret\")}/\u003e\n       // but to use it in \"string\" attribures you have to use render props\n    )}\n    \u003c/WithPlaceholder\u003e\n  \u003c/CachedLocation\u003e\n)\n```   \n    \n#### NotCacheable\nSometimes you might got something, which is not cacheable. \nSometimes cos you better not cache like - like personal information.\nSometimes cos it reads data from variable sources and could not be \"just cached\".\nIt is always __hard to manage__ it. So - just dont cache. It's a one line fix.\n\n```js\nimport {NotCacheable, notCacheable} from 'react-prerendered-component';\n\nconst SafeCache = () =\u003e (\n  \u003cNotCacheable\u003e\n    \u003cYourComponent /\u003e\n  \u003c/NotCacheable\u003e\n);\n\nconst SafeComponent = notCacheable(YourComponent);\n```\n  \n#### Sharing cache between multiple process\nAny network based caches are not supported, the best cache you can use - LRU, is bound to single\nprocess, while you probably want multi-threaded(workers) rendering, but dont want to maintain \nper-instance cache.\n\nYou may use nodejs shared-memory libraries (not supported by nodejs itself), like:\n - https://github.com/allenluce/mmap-object \n\n#### Cache speed\nResults from rendering a single page 1000 times. All tests executed twice to mitigate possible v8 optimizations.\n```text\ndry      1013 - dry render to kick off HOT\nbase     868  - the __real__ rendering speed, about 1.1ms per page\ncache    805  - with `cacheRenderedToString` used on uncachable appp\ncache    801  - second run (probably this is the \"real\" speed)\npartial  889  - with `cacheRenderedToString` used lightly cached app (the cost of caching)\npartial  876  - second run\nhalf     169  - page content cached\nhalf     153  - second run\nfull     22   - full page caching\nfull     19   - second run\n```\n- full page cache is 42x faster. 0.02ms per page render\n- half page render is 5x faster.\n- partial page render is 1.1x slower.\n\n#### Prerendered support\nIt is __safe__ to have `prerendered` component inside a cached location.\n\n\n### Additional API\n1. `ServerSideComponent` - component to be rendered only on server. Basically this is PrerenderedComponent with `live=false`\n2. `ClientSideComponent` - component to be rendered only on client. Some things are not subject for SSR.\n`ClientSideComponent` would not be initially rendered with `hydrated` prop enabled \n3. `thisIsServer(flag)` - override server/client flag\n4. `isThisServer()` - get current environment.\n\n## Automatically goes live\nPrerendered component is work only once. Once it mounted for a first time. \n\nNext time another UID will be generated, and it will not find component to match.\nIf prerendered-component could not find corresponding component - it goes live automatically.\n\n## Testing\nIdea about PrerenderedComponent is to render something, and rehydrate it back. You should be able to \nrender the same, using rehydrated data.\n- render\n- restore\n- render\n- compare. If result is equal - you did it right.\n\n## While area is not \"live\" - it's dead\nUntil component go live - it's dead HTML code. You may be make it more alive by\ntransforming HTML to React, using [html-to-react](https://github.com/aknuds1/html-to-react),\nand go live in a few steps.\n\n## Size 🤯\nIs this package 25kb? What are you thinking about?\n\n- no, __this package is just 3kb__ or less - tree shaking is great (but not in a dev mode)\nIt __is__ bigger only on server.  \n\n## See also\n[react-progressive-hydration](https://github.com/GoogleChromeLabs/progressive-rendering-frameworks-samples/tree/master/react-progressive-hydration) - Google IO19 demo is quite similar to `react-prerendered-component`, but has a few differences.\n- does not use _stable uids_, utilizing `__html:''` + `sCU` hack to prevent HTML update\n- uses `React.hydrate` to make component live, breaking connectivity between React Trees\n- while it has some benefits (no real HTML update), it might not be production ready right now \n\n## Licence\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Freact-prerendered-component","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthekashey%2Freact-prerendered-component","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Freact-prerendered-component/lists"}