├── .babelrc ├── .gitignore ├── .netlify └── state.json ├── README.md ├── functions └── server.js ├── netlify.toml ├── package-lock.json ├── package.json ├── public └── index.html ├── src ├── App.server.js ├── Cache.client.js └── index.client.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | ["@babel/transform-runtime", { 8 | "regenerator": true 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .netlify/ 4 | lambda/ 5 | -------------------------------------------------------------------------------- /.netlify/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteId": "accb14ba-d0b3-4ce6-aade-e131ca7f0438" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Server Components on Netlify 2 | 3 | This is a very minimal example of using React Server Components via Netlify Functions 4 | 5 | To run locally, clone this repository and run: 6 | 7 | ``` 8 | npm i 9 | netlify dev 10 | ``` 11 | 12 | And you should have a local env with both the client side application and the server side components rendered via a Netlify functions. 13 | 14 | This is aiming to be a much simpler starting point than the much more involved [full demo](https://github.com/reactjs/server-components-demo) from the core React team 15 | 16 | [](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/react-server-components-demo) 17 | -------------------------------------------------------------------------------- /functions/server.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {pipeToNodeWritable} from 'react-server-dom-webpack/writer.node.server'; 3 | import App from '../src/App.server'; 4 | 5 | class Writer { 6 | constructor() { 7 | this.buffer = []; 8 | this.result; 9 | this.handlers = {} 10 | } 11 | write(s) { 12 | this.buffer.push(s.toString()) 13 | } 14 | end() { 15 | const handlers = this.handlers.done; 16 | if (handlers) { 17 | handlers.forEach((fn) => { 18 | fn(this.buffer.join()) 19 | }) 20 | } 21 | } 22 | on(event, fn) { 23 | const chain = this.handlers[event] = this.handlers[event] || [] 24 | chain.push(fn) 25 | } 26 | } 27 | 28 | exports.handler = async function(event, context) { 29 | const writer = new Writer; 30 | const props = JSON.parse(event.queryStringParameters.props); 31 | 32 | return new Promise((resolve, reject) => { 33 | writer.on('done', (result) => resolve({ 34 | statusCode: 200, 35 | body: result, 36 | headers: {'Content-Type': 'application/text', 'X-Props': JSON.stringify(props)} 37 | })) 38 | pipeToNodeWritable(React.createElement(App, props), writer, {}) 39 | }) 40 | } -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "dist" 4 | functions = "lambda" 5 | 6 | [dev] 7 | command = "npm run serve" 8 | port = 8888 9 | targetPort = 8080 10 | framework = "#custom" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sc-from-scratch", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack && netlify-lambda build functions && cp public/index.html dist/", 8 | "serve": "webpack serve" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@babel/core": "^7.12.10", 15 | "@babel/plugin-transform-runtime": "^7.12.10", 16 | "@babel/preset-env": "^7.12.11", 17 | "babel-loader": "^8.2.2", 18 | "netlify-lambda": "^2.0.2", 19 | "react": "0.0.0-experimental-3310209d0", 20 | "react-dom": "0.0.0-experimental-3310209d0", 21 | "react-server-dom-webpack": "0.0.0-experimental-3310209d0", 22 | "webpack": "^5.11.0", 23 | "webpack-dev-server": "^3.11.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/preset-react": "^7.12.10", 27 | "webpack-cli": "^4.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Server Components 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/App.server.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function App(props) { 4 | return

Hello {props.name}

5 | } -------------------------------------------------------------------------------- /src/Cache.client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | import {unstable_getCacheForType, unstable_useCacheRefresh} from 'react'; 10 | import {createFromFetch} from 'react-server-dom-webpack'; 11 | 12 | function createResponseCache() { 13 | return new Map(); 14 | } 15 | 16 | export function useRefresh() { 17 | const refreshCache = unstable_useCacheRefresh(); 18 | return function refresh(key, seededResponse) { 19 | refreshCache(createResponseCache, new Map([[key, seededResponse]])); 20 | }; 21 | } 22 | 23 | export function useServerResponse(props) { 24 | const key = JSON.stringify(props); 25 | const cache = unstable_getCacheForType(createResponseCache); 26 | let response = cache.get(key); 27 | if (response) { 28 | return response; 29 | } 30 | response = createFromFetch( 31 | fetch('/.netlify/functions/server?props=' + encodeURIComponent(key)) 32 | ); 33 | cache.set(key, response); 34 | return response; 35 | } 36 | -------------------------------------------------------------------------------- /src/index.client.js: -------------------------------------------------------------------------------- 1 | import React, {Suspense} from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {useServerResponse} from './Cache.client'; 4 | 5 | const title = 'React Server Components on Netlify'; 6 | 7 | const Message = (props) => { 8 | const response = useServerResponse(props) 9 | return response.readRoot() 10 | } 11 | 12 | ReactDOM.render( 13 | Loading...}> 14 |
15 |

{title}

16 | 17 | 18 |
19 |
, 20 | document.getElementById('app') 21 | ); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: path.resolve(__dirname, './src/index.client.js'), 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.(js|jsx)$/, 9 | exclude: /node_modules/, 10 | use: ['babel-loader'], 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['*', '.js', '.jsx'], 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, './dist'), 19 | filename: 'bundle.js', 20 | }, 21 | devServer: { 22 | contentBase: path.resolve(__dirname, './dist'), 23 | }, 24 | }; 25 | --------------------------------------------------------------------------------