Move over Next.js and Webpack!! Simple Streaming SSR React with Styled-Components and Parcel ![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/1znwwnxtmirlftagxgli_lw.png]] ![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/embedded.svg]] Patrick Lee Scott Jan 15·14 min read ![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/unknown_filename.5.png]]![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/1xa00gg1teapto3a_cz26oa.png]] Photo via my Adobe Stock Photo license. One of the things I loved about Next.js when I first used it was that it made the massive boilerplate required by Webpack almost disappear. It also prescribed simple, logical conventions that if you followed, allowed you to be easily successful. I found it to be a huge step up in simplicity compared to the previous complexity of creating Server Side Rendered (SSR) React applications. However, early last year I became aware of a new tool that could solve the same issues for me while staying closer to the core React API. One of my biggest gripes with Next.js is it’s custom routing — although simple to use — the alternative React Router is really great, and there are great animation libraries that go with it, and I like creating pretty lookin’ easy to use things! So in early 2018 I ditched Next.js and Webpack for something a bit “closer to the metal” and started building React apps with Parcel. In this article I want to show you how I’ve been building apps with Parcel to create streaming server side rendered react apps with styled-components. If you’re wondering what I’m so excited or haven’t tried out Parcel yet — Parcel is a newer module bundler in Javascript Land. “Great another tool I have to learn” You think. Nah. Parcel doesn’t roll like that. It’s ZERO-CONFIG. It just works. You can import .css files, images, and whatever else you want and it works exactly like you’d expect it to. This makes it really easy to make universal applications that use all of the latest and greatest in the React ecosystem — code-splitting, streaming rendering, and even differential bundling — making it easy to get the latest in performance optimizations with very little effort! I would like to use the new React lazy and Suspense APIs to implement the code-splitting, however, it’s still not supported on the server side, so we’ll be using a similar alternative. In some cases it still may be slightly more verbose than Next.js, but for my use cases, I prefer the additional customizability. I think you will be surprised to see how simple things have gotten if it’s been awhile since you’ve evaluated your tooling. This is intended for you to be able to follow along and end up with a nice new boilerplate. I always have a personal goal for keeping things as lightweight as possible. If this weren’t SSR, I’d recommend checking out Hyperapp instead of React at all. I built a really cool JS SDK for a Shopify plugin that gave machine learning recommendations using it over the summer. So what are we waiting for? Let’s get started! 1. Setup First, create a new project with the following directory structure — one file, two folders. - app/- server/.gitignore We will make a directory called stream-all-the-things with mkdir. Then we will cd into that directory and create a folder called app and a folder called server. Lastly, we will use touch to create our .gitignore file. Here’s a quick little snippet to do it. Feel free to type each line or copy and paste the whole thing into your terminal. mkdir stream-all-the-things && cd stream-all-the-thingsmkdir appmkdir servertouch .gitignore Here’s the contents for our .gitignore node_modules*.log.cachedist Next, let’s install the dependencies we will need. npm init npm i –save react react-dom react-router styled-components react-helmet-async react-imported-component npm i –save-dev parcel-bundler react-hot-loader Alright, a bit to unpack there. Though not much you haven’t seen before. There’s the base dependencies you’ve probably used before… react, react-dom, plus react-router. Then we also have styled-components to take advantage of its streaming rendering support. Beyond the fact that styled-components is a CSS-in-JS library that supports streaming rendering, I already preferred styled-components! It’s opinionated approach helps to enforce best practices as well as being friendly to CSS developers. react-helmet-async is an async version of the popular library react-helmet that works with streaming SSR. It allows you to change information of in the head of the HTML document as you navigate. For instance, to update the title of the page. Also, we have parcel-bundler which will do the bundling, cross-env to nip some problems with Windows in the bud,nodemon, for our developing our server, react-hot-loader for developing our client, and rimraf for cleaning up. 2. Development mode with parcel Seems how our goal is to develop, let’s start with development mode. Add a dev script in your scripts section of package.json. “scripts”: { “dev”: “parcel app/index.html”} With Parcel, you simple give it the entrypoint to your application as the only argument to start developing. Now let’s create that app/index.html file we referenced. In it, another reference to a file which we have not yet created: client.js. This is the entrypoint to our client application. In other words, the starting point. This is where out initial tree will be rendered. Let’s create app/client.js and then I will break it down. import React from ‘react’import ReactDOM from ‘react-dom’import App from ‘./App’import { HelmetProvider } from ‘react-helmet-async’; const element = document.getElementById(‘app’)const app = ( ) ReactDOM.render(app, element) // Enable Hot Module Reloadingif (module.hot) { module.hot.accept();} And lastly, before we can test anything out, we also need app/App.jsx. import React from ‘react’import Helmet from ‘react-helmet-async’ const App = () => ( Home Page Follow me at @patrickleet ) export default App Now, you should be able to run npm run dev to start your development server with hot code reloading! ➜ npm run dev > stream-all-the-things@1.0.0 dev Users/me/dev/patrickleet/stream-all-the-things> parcel app/index.html Server running at http://localhost:1234✨ Built in 192ms. Let’s check it out! ![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/unknown_filename.2.png]]![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/1chmo_giazp4bep5v84wrda.png]] Because you are not me, try updating the page to a link of your own, and notice that you do not have to reload to see your changes! 3. Add some style I use a mix of global styles, and styled-components. Let’s add in some base resets and styles, as well as define a couple of useful CSS variables that will mathematically help us on our upcoming design adventures. Create a file styles.js: import { createGlobalStyle } from ‘styled-components’ export const GlobalStyles = createGlobalStyle`/* Base 10 typography scale courtesty of @wesbos 1.6rem === 16px */html { font-size: 10px;} body { font-size: 1.6rem;} /* Relative Type Scale *//* https://blog.envylabs.com/responsive-typographic-scales-in-css-b9f60431d1c4 */:root { –step-up-5: 2em; –step-up-4: 1.7511em; –step-up-3: 1.5157em; –step-up-2: 1.3195em; –step-up-1: 1.1487em; /* baseline: 1em */ –step-down-1: 0.8706em; –step-down-2: 0.7579em; –step-down-3: 0.6599em; –step-down-4: 0.5745em; –step-down-5: 0.5em; /* Colors */ –header: rgb(0,0,0);} /* https://css-tricks.com/snippets/css/system-font-stack/ *//* Define the “system” font family *//* Fastest loading font - the one native to their device */@font-face { font-family: system; font-style: normal; font-weight: 300; src: local(".SFNSText-Light"), local(".HelveticaNeueDeskInterface-Light"), local(".LucidaGrandeUI"), local(“Ubuntu Light”), local(“Segoe UI Light”), local(“Roboto-Light”), local(“DroidSans”), local(“Tahoma”);} /* Modern CSS Reset *//* https://alligator.io/css/minimal-css-reset/ */body, h1, h2, h3, h4, h5, h6, p, ol, ul, input[type=text], input[type=email], button { margin: 0; padding: 0; font-weight: normal;} body, h1, h2, h3, h4, h5, h6, p, ol, ul, input[type=text], input[type=email], button { font-family: “system”} *, *:before, *:after { box-sizing: inherit;} ol, ul { list-style: none;} img { max-width: 100%; height: auto;} /* Links */a { text-decoration: underline; color: inherit; &.active { text-decoration: none; }} ` In app/App.jsx import GlobalStyles: import { GlobalStyles } from ‘./styles’ And then change App to render the GlobalStyles component. ![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/unknown_filename.1.png]]![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/1kpdhmvej4ygmwc9hrvzuqa.png]] const App = () => ( Follow me at @patrickleet ) Your app should look slightly less ugly. 4. Routing The next thing we need is for pages to be easy. Let’s add in React Router. In your client we need to import the BrowserRouter from React Router, and then simply wrap our app with it. In app/client.js import { BrowserRouter } from ‘react-router-dom’ // … const app = ( ****) Now in app/App.jsx we need to extract our current content into a new component and load in through the router instead. Let’s start with creating a new page, using pretty much the same content as we have in App.jsx currently. Create app/pages/Home.jsx: import React from ‘react’import Helmet from ‘react-helmet-async’ const Home = () => ( Home Page Follow me at @patrickleet ) export default Home Then, modify App.jsx to have the following content: import React from ‘react’import { Switch, Route, Redirect } from ‘react-router-dom’import Home from ‘./pages/Home’ const App = () => ( ) export default App Now when we run our app, it should look the same as before, except this time it is rendering through our router based on the match of the route /. ![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/unknown_filename.png]]![[./resources/simple-streaming-ssr-react-with-styled-components-.resources/1vryhf1ww_b-ifkkq0ubduw.png]] Before we move on, let’s add a second route, but this time with “code splitting”. Let’s create a second page, app/pages/About.jsx: import React from ‘react’import Helmet from ‘react-helmet-async’ const About = () => ( About Page This is the about page ) export default About And a loading component at app/pages/Loading.jsx: import React from ‘react’ const Loading = () => ( Loading… )export default Loading And finally an Error Component at app/pages/Error.jsx: import React from ‘react’ const Error = () => ( Error! )export default Error To import it, I’d like to make use of the new React.lazy and Suspense APIs, unfortunately, while they will work on the client, once we get to Server Side Rendering we will find that ReactDomServer does not yet support Suspense. Instead, we will rely on another library called react-imported-component which will work with client side and server side rendered apps. Here’s our updated app/App.jsx: import React from ‘react’import { Switch, Route, Redirect } from ‘react-router-dom’;**import importComponent from ‘react-imported-component’;**import Home from ‘./pages/Home.jsx’import LoadingComponent from ‘./pages/Loading’import ErrorComponent from ‘./pages/Error’ const About = importComponent(() => import("./pages/About"), { LoadingComponent, ErrorComponent}); const App = () => (