Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
.parcel-cache
# Deno
deno.lock
# Bun
bun.lock
4 changes: 4 additions & 0 deletions bun/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md
7 changes: 7 additions & 0 deletions bun/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
.env
/node_modules/

# React Router
/.react-router/
/build/
23 changes: 23 additions & 0 deletions bun/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM oven/bun:1-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN bun ci

FROM oven/bun:1-alpine AS production-dependencies-env
COPY ./package.json bun.lock /app/
WORKDIR /app
RUN bun ci --omit=dev

FROM oven/bun:1-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN bun run build

FROM oven/bun:1-alpine
COPY ./package.json bun.lock /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
COPY --from=build-env /app/server.ts /app/server.ts
WORKDIR /app
CMD ["bun", "--trace-uncaught", "--trace-warnings", "run", "/app/server.ts", "build/server/index.js"]
89 changes: 89 additions & 0 deletions bun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Welcome to React Router!

A modern, production-ready template for building full-stack React applications using React Router and Bun.

## Features

- 🚀 Server-side rendering
- ⚡️ Hot Module Replacement (HMR)
- 📦 Asset bundling and optimization
- 🔄 Data loading and mutations
- 🔒 TypeScript by default
- 🎉 TailwindCSS for styling
- 📖 [React Router docs](https://reactrouter.com/)

## Getting Started

### Installation

Install the dependencies:

```bash
bun install
```

### Development

Start the development server with HMR:

```bash
bun run dev
```

Your application will be available at `http://localhost:5173`.

## Building for Production

Create a production build:

```bash
bun run build
```

## Deployment

### Docker Deployment

To build and run using Docker:

```bash
docker build -t my-app .

# Run the container
docker run -p 3000:3000 my-app
```

The containerized application can be deployed to any platform that supports Docker, including:

- AWS ECS
- Google Cloud Run
- Azure Container Apps
- Digital Ocean App Platform
- Fly.io
- Railway

### DIY Deployment

If you're familiar with deploying Bun applications, the built-in app server is production-ready, even though it is
still recommended to use a dedicated static file server for production, e.g. nginx, with gzip compression enabled.

Make sure to deploy the output of `bun run build`

```
├── package.json
├── bun.lock
├── server.ts
├── build/
│ ├── client/ # Static assets
│ └── server/ # Server-side code
```

Then, run `bun run server.ts build/server/index.js`

## Styling

This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.

---

Built with ❤️ using React Router.
15 changes: 15 additions & 0 deletions bun/app/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import "tailwindcss";

@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}

html,
body {
@apply bg-white dark:bg-gray-950;

@media (prefers-color-scheme: dark) {
color-scheme: dark;
}
}
12 changes: 12 additions & 0 deletions bun/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { startTransition, StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { HydratedRouter } from 'react-router/dom'

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter />
</StrictMode>,
)
})
53 changes: 53 additions & 0 deletions bun/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { isbot } from 'isbot'
import { renderToReadableStream } from 'react-dom/server'
import type { EntryContext, HandleErrorFunction } from 'react-router'
import { ServerRouter } from 'react-router'

export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
// loadContext: AppLoadContext,
) {
let shellRendered = false
const userAgent = request.headers.get('user-agent')

const body = await renderToReadableStream(<ServerRouter context={routerContext} url={request.url} />, {
onError(error: unknown) {
responseStatusCode = 500
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error('Streaming rendering error', error)
}
},
})

shellRendered = true

// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
await body.allReady
}

responseHeaders.set('Content-Type', 'text/html')
return new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
})
}

/**
* This function is called whenever React Router catches an error in your application on the server.
* @see https://reactrouter.com/how-to/error-reporting
*/
export const handleError: HandleErrorFunction = (error: unknown, { request }: { request: Request }) => {
if (request.signal.aborted) {
// React Router may abort some interrupted requests, don't log those as errors
return
}
console.error('Uncaught Error', error)
}
75 changes: 75 additions & 0 deletions bun/app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";

import type { Route } from "./+types/root";
import "./app.css";

export const links: Route.LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
},
];

export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

export default function App() {
return <Outlet />;
}

export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;

if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}

return (
<main className="pt-16 p-4 container mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}
3 changes: 3 additions & 0 deletions bun/app/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { type RouteConfig, index } from "@react-router/dev/routes";

export default [index("routes/home.tsx")] satisfies RouteConfig;
13 changes: 13 additions & 0 deletions bun/app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Route } from "./+types/home";
import { Welcome } from "../welcome/welcome";

export function meta({}: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}

export default function Home() {
return <Welcome />;
}
23 changes: 23 additions & 0 deletions bun/app/welcome/logo-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions bun/app/welcome/logo-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading