Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
create-react-app-ssr
Advanced tools
Server Side Rendering for CRA 2.x (with redux, router, code splitting, ...)
This package provides a couple of useful tools to take your CRA to the next level and work out a fully fledged modern universal PWA.
NOTE: this is an holistic approach based on years of attempts to find a decent developer experience with the teams I've been workign on.
I try my best to step out the way and write as little custom code as possible, leveraging as much as I can on existing good and widely used libraries, but this is my personal point of view of what a modern Javascript web appication should look like.
You may like it, you may not.
Here I propose some step by step tutorials that take a CRA 2.x
end enrich it
with the good libraries that we already know, taking away the wiring from you
(and myself) because I like to focus on business values instead of the
infrastructure.
who am I kidding? I love working on the infrastructure! But at one point I also have to step into some "getting things done" mood, and when I do I would like to have stuff that "just works", this thing "just darn works".
All the things that we are going to try to do together originates from a
CRA 2.x
app, so all the folder structures and relative paths that you are
going to see in this document are based on you doing:
npx create-react-app my-app
So the way I suggest you to think about your application folder strategy is something like this:
In this tutorial we take the basic CRA structure and add:
Create /src/app/state.js
and write the basics:
import { createAppStateFactory} from "create-react-app-ssr/lib/create-app-state-factory"
export const createState = createAppStateFactory()
here we are using the createAppStateFactory
utility that generates a function that has the final resposability to create the state for your app.
Why all this s**t?
But for SSR compatibility of course!
When you will run this app on the server you need a clojure around your state manager so multiple concurrent requests that run async interlaced pieces of logic will not leak data into each other!
In /src/index.js
we now need to implement this state with our app:
import { createState } from "./app/state"
createState()
.then(props => ReactDOM.render(<App {...props} />, document.getElementById('root')))
.catch(err => console.log(err))
createState
will provide our state clojure. It is an asynchronous function because it will boot up all the features that we might like to add to our app (we will get there.)
The problem, you already know, is that we need to wrap our App
with a redux provider so to be able to access the state and do cool stuff. Our next step is to create a /src/app/Root.js
file that serves as main entry point and general wrapper of our business logic:
/* eslint react/prop-types: off */
import React from "react"
import { Provider } from "react-redux"
import App from "../App"
const Root = ({ store, ...props }) => (
<Provider store={store}>
<App {...props} />
</Provider>
)
export default Root
and then change our /src/index.js
so that it uses Root
instead of App
:
import { createState } from "./app/state"
import { Root } from "./app/Root"
createState()
.then(props => ReactDOM.render(<Root {...props} />, document.getElementById('root')))
.catch(err => console.log(err))
So far so good, if everything is still working you have now a CRA that is also provided with a redux store.
We didn't add any reducer so far, but if you investigate the state with the dev tools (which are set up by default) you may notice a ssr
state property. This was injected by create-react-app-ssr
and has some useful stuff to help you deal with backend requests and server side rendering of asynchronous components (we will get to it).
Anyway, we can use it to test if our app is correctly connected. Let's modify the basic App.js
component and create a super simple connected component (aka container) that shows something from the state:
import React from "react"
import { connect } from "react-redux"
const mapState = ({ ssr }) => ({
title: "My test app",
rootUrl: ssr.getRootUrl(""),
})
const App = ({ title, rootUrl }) => (
<div>
<h4>{title}</h4>
<code>rootUrl: {rootUrl}</code>
</div>
)
export default connect(mapState)(App)
The expected result is something similar to this screenshot:
The idea here is to encapsulate parts of your development efforts into isolated folders. You and I should be able to work on 2 different features without fear of stepping onto each other's shoes. It doesn't matter how good we are ar resolving git conflicts, avoiding conflicts saves time.
Let's first create an empty feature and register it into our state manager, just to get the structure of it out of our way:
In /src/features/users/index.js
paste:
export const reducers = {}
export const services = []
export const listeners = []
And in /src/features/index.js
paste:
export default [
require("./users"),
]
Last mechanical step is to add the features into our state manager creator, open /src/app/state.js
and change it to:
import { createAppStateFactory} from "create-react-app-ssr/lib/create-app-state-factory"
import features from "../features"
export const createState = createAppStateFactory({}, features)
If everything works... you shouldn't see anything... because we simply registered an empty feature that does nothing. But you should also see no errors in the console. In the next step we are going to add our first reducer to a feature
Our feature is named "users" so it's likely handling users. Let's add a "users" reducer that help us managing this kind of data.
Create /src/features/users/users.reducer.js
:
export const initialState = {
list: [],
}
/**
* Actions
*/
export const ADD_USER = "addUser@users"
export const addUser = user => ({
type: ADD_USER,
payload: user,
})
/**
* Handlers
*/
export const actionHandlers = {
[ADD_USER]: (state, { payload }) => ({
...state,
list: [ ...state.list, payload ],
})
}
export const reducer = (state = initialState, action) => {
const handler = actionHandlers[action.type]
return handler ? handler(state, action) : state
}
export default reducer
And register it into the feature's manifest /src/features/users/index.js
:
export const reducers = {
users: require("./users.reducer").default,
}
If you reload you app (well, it should reload by herself) you should finally be able to see "users" as key of your app's state in the redux dev tools!
A service is just a nice name for asynchronous redux actions. Services are basically collections of thunks, other pieces of the application import those functions and dispatch them (probably from a connected component).
The main thing about a feature's service is that you can export 2 particular functions, init()
and start()
that allow you to hook into the lifecycle of your application. All the init()
functions will be executed and only after that all the start()
. This will give you a predictable behaviour that you can use to setup the stage for your services.
IMPORTANT - SSR: I've seen many times people using the module's scope to save variables that are later on used in different functions. This behaviour works great in the client, because I have my browser and you have yours, but it's a very different story when your app is server side rendered. Keep in mind that the only scope that you can play with is the redux state. If you need local data to be persisted across different functions, use a reducer.
Our first service will simulate a backend load action to retrieve a bounch of users. Create /src/features/users/users.service.js
:
import { addUser } from "./users.reducer"
// this thunk will be used to retreive stuff from the server
// so far is just dummy data, we will move it to the backend.
export const fetchUsers = () => () => {
const dummyData = [{
id: 1,
username: "Luke Skywalker",
}, {
id: 2,
username: "Ian Solo",
}]
return new Promise((resolve) => {
setTimeout(() => resolve(dummyData), 1000)
})
}
// fetch data and deals with the reducer
// here we should also take care of error handling
export const loadUsers = () => async (dispatch) => {
const users = await dispatch(fetchUsers())
users.map(user => dispatch(addUser(user)))
}
// hook into life cycle and load users when the application boot
export const start = () => (dispatch) => {
dispatch(loadUsers())
}
At this point we only need to register the service and we will be able to "load users" when the application boot. Edit /src/features/users/index.js
with the proper change:
export const services = [
require("./users.service"),
]
Now is the time to complete the data management part of your feature with some pub/sub capabilities. Let's say that when the users are loaded for the first time, we want to notify the rest of the application, so that other features can do something with it.
You know already that we will simply dispatch a redux action, but I suggest you keep good track of this actions in a specific file called /src/features/users/events.js
:
export const USERS_FIRST_LOAD = "@@USERS::FIRSTLOAD"
export const usersFirstLoad = (usersList) => ({
type: USERS_FIRST_LOAD,
payload: usersList,
})
This file will list all the redux events that your feature is capable of emitting. Consider it just anothe simple manifest file, something that will improve the quality of your codebase and reduce the amount of strings that you need to copy all over the place.
Now let's modify the loadUsers
so to identify a first load and emit this event in /src/features/users/users.service.js
:
import { usersFirstLoad } from "./events"
...
export const loadUsers = () => async (dispatch, getState) => {
const isFirstLoad = getState().users.list.length === 0
const users = await dispatch(fetchUsers())
users.map(user => dispatch(addUser(user)))
// dispatch an event that is meant for other features to react to
if (isFirstLoad) dispatch(usersFirstLoad(users))
}
NOTE: a redux event is just a normal redux action to which we choose to give a particular meaning, nothing more than that :-)
In order to tackle the listening and reacting to events it's more efficient to try to add a new cool feature to our app. A loading curtain has the scope of masquerading the UI while some initial operations are taking place.
We are going to something extremely simple, we will write a reducer, a container and a listener. But in order to start try to use what you learned so far and scaffold a curtain
feature.
This is going to be trivial. A loading curtain is a piece of UI that need to be visible or not, based on some conditions. So in this reducer we are simply going to store the visibility value. Create /src/features/curtain/curtain.reducer.js
and register it to your feature:
export const initialState = true
/**
* Actions
*/
export const SET_VALUE = "setValue@curtain"
export const setValue = value => ({
type: SET_VALUE,
payload: Boolean(value),
})
/**
* Handlers
*/
export const actionHandlers = {
[SET_VALUE]: (state, { payload }) => payload
}
export const reducer = (state = initialState, action) => {
const handler = actionHandlers[action.type]
return handler ? handler(state, action) : state
}
export default reducer
This piece of UI simply need to cover everything and link it's visibility to the current curtain state value. Create /src/features/curtain/Curtain.js
:
import React from "react"
import { connect } from "react-redux"
const mapState = ({ curtain }) => ({
isVisible: curtain,
})
const style = {
position: "fixed", top: 0, bottom: 0, left: 0, right: 0,
display: "flex", justifyContent: "center", alignItems: "center",
backgroundColor: "#258cf9", color: "#fff",
}
const Curtain = ({ isVisible }) => isVisible
? <div style={style}>loading...</div>
: null
export default connect(mapState)(Curtain)
Now, in order to keep good control over our feature system, I suggest you export every feature capability from the feature's index.js
itself. Edit /src/features/curtain/index.js
and add:
export { default as Curtain } from "./Curtain"
so that we can import it from the /src/App.js
and drop the Curtain
UI into the main app:
import { Curtain } from "./features/curtain"
...
const App = ({ title, rootUrl }) => (
<div>
<h4>{title}</h4>
<code>rootUrl: {rootUrl}</code>
<Curtain />
</div>
)
Finally we are getting to the point of having a listener! What we want to achieve is that after the users first load, the curtain will go away.
Create the listener file in /src/features/curtain/curtain.listener.js
:
import { setValue } from "./curtain.reducer"
export default [
{
type: "@@USERS::FIRSTLOAD",
handler: (action) => (dispatch) => {
console.log("An action was dispatched", action)
dispatch(setValue(false))
},
},
]
And register it into the feature's manifest /src/features/curtain/index.js
:
export const listeners = [
require("./curtain.listener").default,
]
This should be enough to get the Curtain to disappear after the fake loading time of 1s that we set in the users' service.
FAQs
Server Side Rendering for CRA 2.x (with redux, router, code splitting, ...)
The npm package create-react-app-ssr receives a total of 9 weekly downloads. As such, create-react-app-ssr popularity was classified as not popular.
We found that create-react-app-ssr 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.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.