Learn more about Remix Stacks.
npx create-remix@latest --template simple-innovation/bluegrass-stack
This project is based on the work done by Jeff Emery in his Remix on Azure Static Web Apps project.
-
Azure Static Web Apps to host the static content
-
Azure Function API to intercept the routes
-
Remix Server Adapter used by the Azure Function API to handle server requests and responses
-
Remix Server Adapter for Azure Functions package which maps the Azure Function to the browser's Fetch API
-
Static Web Apps CLI for local development and debugging.
-
Production-ready SQLite Database
-
GitHub Actions for deploy on merge to production and staging environments
-
Email/Password Authentication with cookie-based sessions
-
Database ORM with Prisma
-
Styling with Tailwind
-
End-to-end testing with Cypress
-
Local third party request mocking with MSW
-
Unit testing with Vitest and Testing Library
-
Code formatting with Prettier
-
Linting with ESLint
-
Static Types with TypeScript
Not a fan of bits of the stack? Fork it, change it, and use npx create-remix --template your/repo! Make it your own.
The server code in ./api/request-handler/index.ts imports the Remix Server Adapter for Azure Functions and exports the handler that is built by requiring the Remix Application server code compiled into ./api/build.
The server needs the application route information, the Remix server code and the server adapter package. These pieces create the server function that respond to client route and data requests. The Remix Server code is built to be deployed into the Azure Function API. The ./api folder contains configuration data to build the Azure Function independently of the Remix application.
The first step in preparing the server is to build the Remix App so the route information and server code can be made available to the server function.
/package.json defines the script to build the Remix application.
"scripts": {
"build:app": "remix build",
},Remix uses /remix.config.js to set the location of the server code to
./api/build/index.js.
module.exports = {
serverBuildPath: 'api/build/index.js',
}The next step is to build the Remix Server Azure Function. The ./api folder
contains the source and configuration to build and deploy the Azure Function.
The ./api/package.json file defines the build packages and script.
"scripts": {
"build:api": "tsc",
},
"dependencies": {
"@remix-run/node": "^1.5.1",
"@remix-run/react": "^1.5.1",
"@remix-run/serve": "^1.5.1",
"remix-azure": "^0.0.1-alpha.1"
},api/request-handler/index.ts is the source for the Remix Server Azure
Function. The Remix application code from ./api/build/index.js is required
into the Remix Server handler. The Remix Server handler is provided by
Remix Server Adapter for Azure Functions.
The tsconfig.json file sets the output directory to ./api/dist.
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "dist",
"rootDir": ".",
"sourceMap": true,
"strict": false,
"esModuleInterop": true
}
The ./api/request-handler/function.json file configures the Azure Function
runtime and entry point, When deployed, Azure looks for folders with a
function.json file and uses it to publish the function. The scriptFile
property defines the function entry point ../dist/request-handler/index.js.
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get", "post"]
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
],
"scriptFile": "../dist/request-handler/index.js"
}Azure Static Web Apps handle routes by rewriting URLs to the Azure Function API.
Static assets have a route defined in ./public/staticwebapp.config.json. A
wildcard route maps the rest of the application routes through the Azure
Function API.
"routes": [
{
"route": "/favicon.ico"
},
{
"route": "/build/*"
},
{
"route": "/*",
"rewrite": "/api/request-handler"
}
]The Azure Static Web App CLI allows the developer to build and debug the application locally with an emulator. /swa-cli.config.json configures the SWA CLI commands for building and running the emulator.
{
"$schema": "https://aka.ms/azure/static-web-apps-cli/schema",
"configurations": {
"remix-client-azure": {
"appName": "remix-client",
"appLocation": ".",
"appBuildCommand": "npm run build:app",
"appDevserverUrl": "http://localhost:3000",
"outputLocation": "public\\build",
"apiLocation": "api",
"apiBuildCommand": "npm run build:api --if-present",
"run": "npm run dev",
"resourceGroupName": "remix-app-rg",
"env": "preview"
}
}
}Running swa build performs the build steps defined in /swa-cli.config.json.
The first step builds the Remix application with npm run build:app defined by appBuildCommand. Static assets are built into ./public/build. Server components are built into ./api/build/index.js.
The second step builds the Azure Function API from the ./api directory with npm run build:api.
Running swa start runs the application in an Azure SWA emulator using the
configuration from /swa-cli.config.json. The Remix dev environment is started
with npm run dev defined by run. The emulator proxies to the Remix dev
server set with appDevserverUrl. This lets the developer
use the Remix framework
for things like 'live reload' and HMR. Azure Function definitions found in
apiLocation are started to serve API requests.
$ swa build
Welcome to Azure Static Web Apps CLI (1.0.2)
Using configuration "remix-client-azure" from file:
C:\code\github\remix-client-azure\swa-cli.config.json
[api] Azure Functions Core Tools
[api] Core Tools Version: 4.0.4736 Commit hash: N/A (64-bit)
[api] Function Runtime Version: 4.8.1.18957
[run]
[run] > remix-client-azure@0.0.1-alpha.0 dev
[run] > remix dev
[api]
[api] Functions:
[api]
[api] request-handler: [GET,POST] http://localhost:7071/api/request-handler
[api]
[api] For detailed output, run func with --verbose flag.
[swa]
[swa] Found configuration file:
[swa] C:\code\github\remix-client-azure\staticwebapp.config.json
[swa]
[swa] - Waiting for http://localhost:3000 to be ready
[api] [2022-09-09T20:04:32.221Z] Worker process started and initialized.
[run] Loading environment variables from .env
[run] Watching Remix app in development mode...
[run] ๐ฟ Built in 626ms
[run] Remix App Server started at http://localhost:3000 (http://192.168.86.193:3000)
[swa] โ Connected to http://localhost:3000 successfully
[swa]
[swa] Using dev server for static content:
[swa] http://localhost:3000
[swa]
[swa] Serving API:
[swa] C:\code\github\remix-client-azure\api
[swa]
[swa] Azure Static Web Apps emulator started at http://localhost:4280.View the Remix application running in the Azure Static Web Apps emulator from http://localhost:4280.
The Remix Server Adapter for Azure Functions package provides a handler to deploy the Remix server functions to Azure Functions on the Node runtime.
Understanding Remix Server Adapters and how Remix runs on the server (The Remix team does not intend to maintain their own Azure Functions adapter.)
Azure Functions provides the Node server environment to handle route and data requests and responses.
The Static Web Apps CLI for development.
Click this button to create a Gitpod workspace with the project set up
-
This step only applies if you've opted out of having the CLI install dependencies for you:
npx remix init
-
Initial setup: If you just generated this project, this step has been done for you.
npm run setup
-
Start dev server:
npm run dev
This starts your app in development mode, rebuilding assets on file changes.
The database seed script creates a new user with some data you can use to get started:
- Email:
rachel@remix.run - Password:
racheliscool
The Azure Static Web App CLI allows the developer to build and debug the application locally with an emulator.
In order to debug the application locally:
- Use Run and Debug
- Select node.js
- Select Run Script: Start
- In a web browser open http://localhost:3000
Place breakpoints in Visual Studio code as normal.
This is a pretty simple note-taking app, but it's a good example of how you can build a full stack app with Prisma and Remix. The main functionality is creating users, logging in and out, and creating and deleting notes.
- creating users, and logging in and out ./app/models/user.server.ts
- user sessions, and verifying them ./app/session.server.ts
- creating, and deleting notes ./app/models/note.server.ts
This Remix Stack comes with two GitHub Actions that handle automatically deploying your app to production and staging environments.
Prior to your first deployment, you'll need to do a few things:
- Initialize Git.
git init-
Create a new GitHub Repository, and then add it as the remote for your project. Do not push your app yet!
git remote add origin <ORIGIN_URL>
If you don't have openssl installed, you can also use 1password to generate a random secret, just replace
$(openssl rand -hex 32)with the generated secret.
Now that everything is set up you can commit and push your changes to your repo. Every commit to your main branch will trigger a deployment to your production environment, and every commit to your dev branch will trigger a deployment to your staging environment.
We use GitHub Actions for continuous integration and deployment. Anything that gets into the main branch will be deployed to production after running tests/build/etc. Anything in the dev branch will be deployed to staging.
We use Cypress for our End-to-End tests in this project. You'll find those in the cypress directory. As you make changes, add to an existing file or create a new file in the cypress/e2e directory to test your changes.
We use @testing-library/cypress for selecting elements on the page semantically.
To run these tests in development, run npm run test:e2e:dev which will start the dev server for the app as well as the Cypress client. Make sure the database is running in docker as described above.
We have a utility for testing authenticated features without having to go through the login flow:
cy.login();
// you are now logged in as a new userWe also have a utility to auto-delete the user at the end of your test. Just make sure to add this in each test file:
afterEach(() => {
cy.cleanupUser();
});That way, we can keep your local db clean and keep your tests isolated from one another.
For lower level tests of utilities and individual components, we use vitest. We have DOM-specific assertion helpers via @testing-library/jest-dom.
This project uses TypeScript. It's recommended to get TypeScript set up for your editor to get a really great in-editor experience with type checking and auto-complete. To run type checking across the whole project, run npm run typecheck.
This project uses ESLint for linting. That is configured in .eslintrc.js.
We use Prettier for auto-formatting in this project. It's recommended to install an editor plugin (like the VSCode Prettier plugin) to get auto-formatting on save. There's also a npm run format script you can run to format all files in the project.
