Skip to content

feat[remix][gen2]: add remix gen2 snippets #3910

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ jobs:
'nextjs-sdk-next-app',
'react-sdk-next-14-app',
'react-sdk-next-pages',
'remix',
'svelte',
'sveltekit',
'solid',
Expand Down
4 changes: 4 additions & 0 deletions packages/sdks/snippets/remix/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md
6 changes: 6 additions & 0 deletions packages/sdks/snippets/remix/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/node_modules/

# React Router
/.react-router/
/build/
22 changes: 22 additions & 0 deletions packages/sdks/snippets/remix/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:20-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN npm ci

FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json /app/
WORKDIR /app
RUN npm ci --omit=dev

FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build

FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
WORKDIR /app
CMD ["npm", "run", "start"]
100 changes: 100 additions & 0 deletions packages/sdks/snippets/remix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Welcome to React Router!

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

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)

## 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
npm install
```

### Development

Start the development server with HMR:

```bash
npm run dev
```

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

## Building for Production

Create a production build:

```bash
npm run build
```

## Deployment

### Docker Deployment

This template includes three Dockerfiles optimized for different package managers:

- `Dockerfile` - for npm
- `Dockerfile.pnpm` - for pnpm
- `Dockerfile.bun` - for bun

To build and run using Docker:

```bash
# For npm
docker build -t my-app .

# For pnpm
docker build -f Dockerfile.pnpm -t my-app .

# For bun
docker build -f Dockerfile.bun -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 Node applications, the built-in app server is production-ready.

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

```
├── package.json
├── package-lock.json (or pnpm-lock.yaml, or bun.lockb)
├── build/
│ ├── client/ # Static assets
│ └── server/ # Server-side code
```

## 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 packages/sdks/snippets/remix/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;
}
}
75 changes: 75 additions & 0 deletions packages/sdks/snippets/remix/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 packages/sdks/snippets/remix/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/builder-page.tsx")] satisfies RouteConfig;
56 changes: 56 additions & 0 deletions packages/sdks/snippets/remix/app/routes/builder-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import {
Content,
fetchOneEntry,
getBuilderSearchParams,
isPreviewing,
type BuilderContent,
} from '@builder.io/sdk-react';

// Builder Public API Key set in .env file
const BUILDER_API_KEY = 'ee9f13b4981e489a9a1209887695ef2b';
const MODEL_NAME = 'page';

export default function BuilderPage() {
const [notFound, setNotFound] = React.useState(false);
const [content, setContent] = React.useState<BuilderContent | null>(null);

// get the page content from Builder
React.useEffect(() => {
fetchOneEntry({
model: MODEL_NAME,
apiKey: BUILDER_API_KEY,
userAttributes: {
urlPath: window.location.pathname,
},
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((content) => {
if (content) {
setContent(content);
}
setNotFound(!content);
})
.catch((err) => {
console.log('Oops: ', err);
});
}, []);

// If no page is found, return
// a 404 page from your code.
if (notFound && !isPreviewing()) {
return <div>404 Page Not Found</div>;
}

// return the page when found
return (
<>
{/* Render the Builder page */}
<Content
content={content}
model={MODEL_NAME}
apiKey={BUILDER_API_KEY}
/>
</>
);
}
47 changes: 47 additions & 0 deletions packages/sdks/snippets/remix/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@snippet/remix",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need a serve command for tests to run

Suggested change
"start": "react-router-serve ./build/server/index.js",
"start": "react-router-serve ./build/server/index.js",
"serve": "yarn start",

"typecheck": "react-router typegen && tsc",
"test": "SERVER_NAME=remix yarn g:nx test:snippet @sdk/tests"
},
"dependencies": {
"@builder.io/sdk-react": "workspace:*",
"@react-router/node": "^7.1.5",
"@react-router/serve": "^7.1.5",
"isbot": "^5.1.17",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.1.5"
},
"devDependencies": {
"@react-router/dev": "^7.1.5",
"@tailwindcss/vite": "^4.0.0",
"@types/node": "^20",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1",
"react-router-devtools": "^1.1.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.2",
"vite": "^5.4.11",
"vite-tsconfig-paths": "^5.1.4"
},
"installConfig": {
"hoistingLimits": "workspaces"
},
"nx": {
"targets": {
"build": {
"outputs": [
"{projectRoot}/build/client",
"{projectRoot}/build/server"
]
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this PR is breaking all tests relying on React.

Probably because this snippet installs a new react version at the root of the monorepo, causing other tests to use it when they should stick to a different version.

See how we configure other remix snippets to scope packages locally:

"installConfig": {
"hoistingLimits": "workspaces"
},

Binary file added packages/sdks/snippets/remix/public/favicon.ico
Binary file not shown.
7 changes: 7 additions & 0 deletions packages/sdks/snippets/remix/react-router.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Config } from "@react-router/dev/config";

export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: true,
} satisfies Config;
27 changes: 27 additions & 0 deletions packages/sdks/snippets/remix/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"include": [
"**/*",
"**/.server/**/*",
"**/.client/**/*",
".react-router/types/**/*"
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}
8 changes: 8 additions & 0 deletions packages/sdks/snippets/remix/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
});
Loading
Loading