Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
react-snap
Advanced tools
Pre-renders a web app into static HTML. Uses Headless Chrome to crawl all available links starting from the root. Heavily inspired by prep and react-snapshot, but written from scratch. Uses best practices to get the best loading performance.
react-snapshot
but works with any technology (e.g., Vue).Zero configuration is the main feature. You do not need to worry about how it works or how to configure it. But if you are curious, here are details.
Install:
yarn add --dev react-snap
Change package.json
:
"scripts": {
"postbuild": "react-snap"
}
Change src/index.js
(for React 16+):
import { hydrate, render } from "react-dom";
const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
hydrate(<App />, rootElement);
} else {
render(<App />, rootElement);
}
That's it!
To do hydration in Preact you need to use this trick:
const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
preact.render(<App />, rootElement, rootElement.firstElementChild);
} else {
preact.render(<App />, rootElement);
}
Install:
yarn add --dev react-snap
Change package.json
:
"scripts": {
"postbuild": "react-snap"
},
"reactSnap": {
"source": "dist",
"minifyHtml": {
"collapseWhitespace": false,
"removeComments": false
}
}
Or use preserveWhitespace: false
in vue-loader
.
source
- output folder of webpack or any other bundler of your choice
Read more about minifyHtml
caveats in #142.
Example: Switch from prerender-spa-plugin to react-snap
Only works with routing strategies using the HTML5 history API. No hash(bang) URLs.
Vue uses the data-server-rendered
attribute on the root element to mark SSR generated markup. When this attribute is present, the VDOM rehydrates instead of rendering everything from scratch, which can result in a flash.
This is a small hack to fix rehydration problem:
window.snapSaveState = () => {
document.querySelector("#app").setAttribute("data-server-rendered", "true");
};
window.snapSaveState
is a callback to save the state of the application at the end of rendering. It can be used for Redux or async components. In this example, it is repurposed to alter the DOM, this is why I call it a "hack." Maybe in future versions of react-snap
, I will come up with better abstractions or automate this process.
Make sure to use replace: false
for root components
If you need to pass some options for react-snap
, you can do this in your package.json
like this:
"reactSnap": {
"inlineCss": true
}
Not all options are documented yet, but you can check defaultOptions
in index.js
.
Experimental feature - requires improvements.
react-snap
can inline critical CSS with the help of minimalcss and full CSS will be loaded in a non-blocking manner with the help of loadCss.
Use inlineCss: true
to enable this feature.
TODO: as soon as this feature is stable, it should be enabled by default.
Also known as code splitting, dynamic import (TC39 proposal), "chunks" (which are loaded on demand), "layers", "rollups", or "fragments". See: Guide To JavaScript Async Components
An async component (in React) is a technique (typically implemented as a higher-order component) for loading components on demand with the dynamic import
operator. There are a lot of solutions in this field. Here are some examples:
It is not a problem to render async components with react-snap
, the tricky part happens when a prerendered React application boots and async components are not loaded yet, so React draws the "loading" state of a component, and later when the component is loaded, React draws the actual component. As a result, the user sees a flash:
100% /----| |----
/ | |
/ | |
/ | |
/ |____|
visual progress /
/
0% -------------/
react-loadable
and loadable-components
solve this issue for SSR. But only loadable-components
can solve this issue for a "snapshot" setup:
import { loadComponents, getState } from "loadable-components";
window.snapSaveState = () => getState();
loadComponents()
.then(() => hydrate(AppWithRouter, rootElement))
.catch(() => render(AppWithRouter, rootElement));
If you don't use babel plugin, don't forget to provide modules:
const NotFoundPage = loadable(() => import("src/pages/NotFoundPage"), {
modules: ["NotFoundPage"]
});
loadable-components
were deprecated in favour of @loadable/component
, but @loadable/component
dropped getState
. So if you want to use loadable-components
you can use old version (2.2.3
latest version at the moment of writing) or you can wait until React
will implement proper handling of this case with asynchronous rendering and React.lazy
.
See: Redux Server Rendering Section
// 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 || initialState);
// Tell react-snap how to save Redux state
window.snapSaveState = () => ({
__PRELOADED_STATE__: store.getState()
});
Caution: as of now, only basic "JSON" data types are supported: e.g. Date
, Set
, Map
, and NaN
won't be handled correctly (#54).
You can block all third-party requests with the following config:
"skipThirdPartyRequests": true
react-snap
can capture all AJAX requests. It will store json
requests in the domain in window.snapStore[<path>]
, where <path>
is the path of the request.
Use "cacheAjaxRequests": true
to enable this feature.
This feature can conflict with the browser cache. See #197 for details. You may want to disable cache in this case: "puppeteer": { "cache": false }
.
By default, create-react-app
uses index.html
as a fallback:
navigateFallback: publicUrl + '/index.html',
You need to change this to an un-prerendered version of index.html
- 200.html
, otherwise you will see index.html
flash on other pages (if you have any). See Configure sw-precache without ejecting for more information.
Puppeteer (Headless Chrome) may fail due to sandboxing issues. To get around this, you may use:
"puppeteerArgs": ["--no-sandbox", "--disable-setuid-sandbox"]
Read more about puppeteer troubleshooting.
"inlineCss": true
sometimes causes problems in containers.
To run react-snap
inside docker
with Alpine, you might want to use a custom Chromium executable. See #93 and #132.
heroku buildpacks:add https://github.com/jontewks/puppeteer-heroku-buildpack.git
heroku buildpacks:add heroku/nodejs
heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static.git
See this PR. At the moment of writing, Heroku doesn't support HTTP/2.
Semantic UI is defined over class substrings that contain spaces (e.g., "three column"). Sorting the class names, therefore, breaks the styling. To get around this, use the following configuration:
"minifyHtml": { "sortClassName": false }
From version 1.17.0
, sortClassName
is false
by default.
Once JS on the client is loaded, components initialized and your JSS styles are regenerated, it's a good time to remove server-side generated style tag in order to avoid side-effects
This basically means that JSS doesn't support rehydration
. See #99 for a possible solutions.
react-router
v3See #135.
You can use navigator.userAgent == "ReactSnap"
to do some checks in the app code while snapping—for example, if you use an absolute path for your API AJAX request. While crawling, however, you should request a specific host.
Example code:
const BASE_URL =
process.env.NODE_ENV == "production" && navigator.userAgent != "ReactSnap"
? "/"
: "http://xxx.yy/rest-api";
See alternatives.
Please provide a reproducible demo of a bug and steps to reproduce it. Thanks!
Tweet it, like it, share it, star it. Thank you.
You can also contribute to minimalcss, which is a big part of react-snap
. Also, give it some stars.
FAQs
Zero-configuration framework-agnostic static prerendering for SPAs
The npm package react-snap receives a total of 9,229 weekly downloads. As such, react-snap popularity was classified as popular.
We found that react-snap demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.