Why Another Boilerplate
The combination of React and TypeScript is very popular and so is Create React App (CRA). Express is a widely used choice for a webserver. Accordingly, there is no shortage of articles describing how to make them work together. So the question is why we need another one?
The answer is multi-faceted:
- Performance. A script bundle with size 3.5 MB in development cut to ~70 KB in production.
- Backend implements HTTP caching that further enhances performance yet supports smooth deployment of versioning changes in production.
- Ability to optionally split your React Application into multiple Single Page Applications (SPA). For example, one SPA can offer an introductory set of screens for the first-time user or handle login. Another SPA could implement the rest of the application, except for Auditing or Reporting that can be catered for by yet another SPA.
- Seamless debugging. Debug a minified/obfuscated, compressed production bundle and put breakpoints in its TypeScript code using both VS Code and Chrome DevTools. Development build debugging: put breakpoints in the client and backend code and debug both simultaneously using a single instance of VS Code.
- Containerisation. Docker multi-staged build is used to ensure the backend run-time environment doesnβt contain the client build-time dependencies e.g.
client/node_modules/
. It improves security and reduces container storage footprint.
Crisp React
I hope this functionality sounds interesting enough to introduce Crisp React to you. Itβs a boilerplate project with all the above features. Start by cloning the repository then build the client and start the backend:
git clone https://github.com/winwiz1/crisp-react.git
cd crisp-react
yarn install && yarn start:prod
If the last command ends in error because yarn is not installed, run npm install yarn -g
and repeat the command.
Once the message "Starting the backend..." appears on the console, navigate a browser to localhost:3000. It should display this page:
The React application built by the preceding yarn start:prod
command consists of two SPAs called 'first' and 'second'. The name determines the landing page of the SPA so we have two pages: /first.html
and /second.html
. The browser is displaying the first landing page but its path is not shown in the navigation bar. The reason is simple: redirection. To understand why is it needed, let's recollect how SPA works.
SPA Background
When you choose ComponentA or ComponentB using the menu, the browser navigates to /a
or /b
. However these paths are internal to the SPA, the backend is not aware of it and keeps 'thinking' there were no page transitions. Suppose a 'curious user' typed in the path /a
and hit Enter. The backend is not prepared to serve anything except for the landing page(s) so it would respond with 404 "Not Found" error and it wouldn't look good. Had the user chosen to refresh the page, the outcome would have been the same.
As a protection against 'curious users' and refreshes, any SPA-aware webserver instead of 404 error uses redirects to the landing page of the SPA. Which in our case is served if browser is manually pointed to /a
or /
or /invalid
etc. Such tolerance usually has its limits due to security considerations and navigating to something like /invalid/valid
does trigger an error.
SPA Customisation
There is hopefully nothing wrong with our two sample SPAs except for the not overtly meaningful names, 'first' and 'second'. Let's rename it to 'login' and 'app' while adding the 3rd 'reporting' SPA. In the file client/config/spa.config.js
modify the SPA Configuration block:
/****************** Start SPA Configuration ******************/
let SPAs = [
new SPA({ name: "first", entryPoint: './src/entrypoints/first.tsx', redirect: true }),
new SPA({ name: "second", entryPoint: './src/entrypoints/second.tsx', redirect: false }),
];
SPAs.appTitle = "Crisp React";
/****************** End SPA Configuration ******************/
by changing the names. Then copy and paste the 2nd SPA configuration to create another SPA. Finally, name the 3rd SPA 'reporting':
/****************** Start SPA Configuration ******************/
let SPAs = [
new SPA({ name: "login", entryPoint: './src/entrypoints/first.tsx', redirect: true }),
new SPA({ name: "app", entryPoint: './src/entrypoints/second.tsx', redirect: false }),
new SPA({ name: "reporting", entryPoint: './src/entrypoints/second.tsx', redirect: false }),
];
SPAs.appTitle = "DemoApp";
/****************** End SPA Configuration ******************/
To try the new SPAs we need to stop the backend we started earlier. Press Control+C
, rebuild and start the backend using another yarn start:prod
command. Then open a private browser tab to avoid caching or history issues related to the old names. Point this tab to localhost:3000 and use the menu to navigate around the app. Note the landing pages have changed to /login.html
and /app.html
. The bundle names are visible via the View Page Source browser's menu and did change as well.
The 3rd SPA is available at /reporting.html
. It is unsurprisingly identical to the 'app' SPA because we copied and pasted its configuration.
If multiple SPAs are not needed, simply comment out or remove all SPA configuration entries except for one, rebuild and you are done.
Press Control+C
to stop the backend. We are going to start it in the next section and don't want to have several backend instances fighting for the same port.
Debugging
Some bugs are tricky to pinpoint and may require setting breakpoints in both the backend and the client app. Let's see how it can be done using Chrome DevTools and VS Code with 'Debugger for Chrome' extension.
To start, restore the SPA configuration file, then build the solution and open the workspace in VS Code:
git checkout ./client/config/spa.config.js
yarn build
code ./crisp-react.code-workspace
In VS Code activity bar on the far left-hand side, click on Debug and start the Debug Client and Backend (workspace)
debugging configuration:
Wait until Chrome starts and displays the landing page of the 'first' SPA we saw earlier. Open Chrome DevTools. On the 'Sources' tab use 'Filesystem' and then 'Add folder to workspace' to add the client/src
directory. Chrome will display a toolbar asking for permissions to access this directory. Click on the 'Allow' button. The content of the added directory will be shown with green dots superimposed on the source file icons:
ComponentC doesn't have a green dot because it cannot be found in the source maps for the currently loaded script bundle. Which makes sense as it belongs to another SPA and its bundle.
Open the file ComponentB.tsx
and put a breakpoint on line 14.
Use the landing page menu to choose the ComponentB. The breakpoint will be hit with the line 14 highlighted in light blue:
VS Code apparently knows about that and displays a yellow hollow marker against the same line. Click on the blue marker to remove the breakpoint and resume execution using either Chrome DevTools or VS Code. Close Chrome DevTools.
Now let's focus on debugging in VS Code. Place a breakpoint on the same line in client/src/components/ComponentB.tsx
and another breakpoint in the file server/src/Server.ts
on line 59.
Note that VS Code can mark a breakpoint with a circle that is grey and hollow rather than red and solid, referring to it as 'Unverified breakpoint'. You can check the breakpoint has been set and marked with solid red circle by highlighting the relevant process (client or server) on VS Code Debug sidebar in the CALL STACK view:
To test both breakpoints, choose the ComponentC from the menu. The server breakpoint will be hit. Remove or disable the breakpoint (unless you are comfortable to keep resuming each time it's hit), resume execution and use the menu to go back to the First SPA. Choose the ComponentB. The breakpoint set in this component will be hit.
If you modify client/src/components/Overview.tsx
, for instance alter the text displayed on the landing page, the code will be recompiled. As you are typing, note the compilation progress in the client Terminal followed by automatic browser refresh. The new content should be shown on the landing page. In a similar manner, modifying server/src/Server.ts
will cause recompilation visible in VS Code in the second (server) Terminal.
To finish debugging stop both client and server processes. Use VS Code Debugging toolbar (click the βStopβ button two times) or press Control+F5
twice.
There are several debugging scenarios described in the project README. We went through one of those, I hope you will find the others to be useful too.
Containerisation
To build and run a Docker container execute start-container.cmd
or start-container.sh
. The file can also be executed from an empty directory in which case uncomment the two lines at the top.
Moreover, it can be copied to a computer or VM that doesn't have NodeJS installed. The only prerequisites are Docker and Git.
Next Steps
After reading the README consider customising SPAs, UI and adding an API endpoint to the backend.
By following the advice in the relevant README section, you should avoid frequent pitfalls of React-Express projects such as getting CORS issues or running webpack-dev-server in production.
Happy Reacting in the crisp clear waters!
Top comments (0)