With Bun's new bundler, it's now possible to build a fullstack React app with server-side rendering entirely within the Bun ecosystem! Let's work an example and see how this looks.
Let's initialize an empty project and install some dependencies.
mkdir my-bun-app
cd my-bun-app
bun init -y
mv index.ts index.tsx
bun add react@next react-dom@next
We now have an index.tsx
file in our project root. Let's write a simple React component and render it to the DOM.
import { createRoot } from "react-dom/client";
export default function App() {
return (
<html>
<body>
<h1>Hello world</h1>
</body>
</html>
);
}
const root = createRoot(document);
root.render(<App />);
This doesn't do anything yet. We need to spin up a simple HTTP server. Next, create a file called server.ts
and paste the following code. This spins up a simple HTTP server that returns an empty HTML document on every request.
Bun.serve({
port: 3000,
async fetch(req) {
return new Response(
`<html>Hello</html>`,
{headers: {'content-type': 'text/html'}}
);
},
});
Let's run the server and see what happens.
bun run server.ts
open http://localhost:3000
You should see a message saying "Hello". This is the simple HTML document we returned from our server.
It's bundling time! Let's wire up our server and our React component. First add a call to Bun.build
above the server. This bundles index.tsx
and writes the result to ./build/index.js
.
const BUILD_DIR = import.meta.dir + '/build';
await Bun.build({
entrypoints: ['./index.tsx'],
outdir: BUILD_DIR,
target: 'browser',
});
Bun.serve({
// ...
})
Finally, let's tweak our server to serve ./build/index.js
and accordingly add a <script>
tag accordingly.
const BUILD_DIR = import.meta.dir + '/build';
await Bun.build({
entrypoints: ['./index.tsx'],
outdir: BUILD_DIR,
target: 'browser',
});
Bun.serve({
port: 4000,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === '/index.js') {
const bundled = Bun.file(BUILD_DIR + '/index.js');
return new Response(bundled);
}
return new Response(
`<html>
<script src="/index.js"></script>
</html>`,
{headers: {'content-type': 'text/html'}}
);
},
});
Refresh the page, and you should see "Hello world", the rendered version of your <App />
component.