Here we builing Proof of Concept for React Server Side Rendereing with create-react-app (preferably without using of eject) and Typescript
https://ssr-example-test.herokuapp.com/
Creating an empty create-react-app project with typescript:
npx create-react-app my-app --template typescriptTemporarily downgrading React to version 18 due to issue when running client (vercel/next.js#35773)
npm i react@17.0.2 react-dom@17.0.2 @types/react@17.0.2 @types/react-dom@17.0.2 -SAdding project depencencies:
npm i app-root-path -S
npm i ts-loader -D
npm i cross-env -S
npm i concurrently -D // runs concurrently several tasks during `npm start`Remove logo.svg, App.css, App.test.tsx, index.css created by create-react-app template and add Counter component.
import React, { useState, useCallback } from "react";
export const Counter = () => {
const [counter, setCounter] = useState(0);
const incrementCounter = useCallback(() => {
setCounter(counter + 1);
}, [counter]);
return (
<div>
<h1>counter at: {counter}</h1>
<button onClick={incrementCounter}>+</button>
</div>
);
};Make sure App imports and renders it.
import React from 'react';
import { Counter } from './Counter';
function App() {
return (
<div>
<header>
<Counter />
</header>
</div>
);
}
export default App;Create file for server code server/index.ts
import path from 'path';
import fs from 'fs';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import express from 'express';
import App from '../src/App';
const PORT = process.env.PORT || 3006;
const app = express();
app.get('/', (req, res) => {
const app = ReactDOMServer.renderToString(<App />);
const indexFile = path.resolve('./build/index.html');
fs.readFile(indexFile, 'utf8', (err, data) => {
if (err) {
console.error('Something went wrong:', err);
return res.status(500).send('Oops, better luck next time!');
}
return res.send(
data.replace('<div id="root"></div>', `<div id="root">${app}</div>`)
);
});
});
app.use(express.static('./build'));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});Important! Make sure process.env.PORT is used to set app port as Heroku will populate this property.
Create file for server config webpack.server.config.js
const root = require('app-root-path').path;
module.exports = {
entry: `${root}/server/index.tsx`,
target: 'node',
node: {
__filename: true,
__dirname: true
},
externals: [
/^[a-z\-0-9]+$/ // Ignore node_modules folder,
],
output: {
filename: 'compiled', // output file
path: `${root}/build_server`,
libraryTarget: "commonjs"
},
resolve: {
modules: [`${root}/node_modules`],
// Add in `.ts` and `.tsx` as a resolvable extension.
extensions: ['.config.js', '.web.js', '.ts', '.tsx', '.js'],
},
module: {
rules: [{
// all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
test: /\.(ts|tsx)$/,
exclude: [/node_modules/],
use: [
{
loader: 'ts-loader'
}
]
}]
}
};Updating package.json scripts section:
"start:server": "cross-env NODE_ENV=development node build_server/compiled",
"build:server": "node node_modules/webpack/bin/webpack.js --config webpack.server.config.js",Important! In tsconfig.json rule "noEmit": true should be removed as it creates no emit error duing server build
Build project (creates build/index.html referenced in server files):
npm run buildAnd then:
npm run build:server
npm run start:serverAfter adding following commands to package.json:
"start": "concurrently \"npm run build && npm run start:server\" \"react-scripts-ts start\"",
"build": "npm run build:client && npm run build:server",The app could be started with:
npm startInstall Heroku CLI, then login into it:
heroku loginImportant! Make sure that Heroku config production mode is set to false, otherwise devDependencies from package.json won't be installed:
heroku config:set NPM_CONFIG_PRODUCTION=falsePush project to Heroku Git:
heroku git:remote -a ssr-example-testPush to Heroku Git triggers npm start and runs the app:
git push heroku master- https://github.com/haukurk/cra-ssr-ts-recipe (has more complex configs incl. routing)
- https://www.digitalocean.com/community/tutorials/react-server-side-rendering (full example didn't work)
- reactwg/react-18#5
- https://betterprogramming.pub/how-to-deploy-your-react-app-to-heroku-aedc28b218ae
- Still having issue with React 18. There is an open issue for that