The Remix Stack for SaaS apps (Web2) and Web3 & Web5 DApps with authentication with Magic, testing, linting, formatting, etc.
MIT License
The Remix Stack for Web2, Web3 and Web5 ๐๐บ
Learn more about Remix Stacks.
npx create-remix --template ten-x-dev/french-house-stack
The French House Stack is a starter template for SaaS apps in general, but also for developing DApps by using Magic. However, Magic is perfectly suited for a regular Web2 app, too.
This stack pinned all version of its dependencies in order to ensure that it always works. You can use
npx npm-check-updates -u
to check for updates and install the latest versions.
Make sure you're using Node.js 20.10.0 or higher. You can run:
node -v
to check which version you're on.
If you need to upgrade, we recommend using
nvm
:
nvm install --lts
nvm use --lts
nvm alias default 'lts/*'
Install dependencies:
npm i
Make sure your system can run the Husky hooks (Mac & Linux):
chmod a+x .husky/pre-commit
chmod a+x .husky/commit-msg
Create a .env
file and add these environment variables (see .env.example
,
too):
MAGIC_PUBLISHABLE_KEY
and MAGIC_SECRET_KEY
- You'll need to grab aSESSION_SECRET
- The session secret can be any string that is at least 32DATABASE_URL
- The url under which the SQLite database will operate. You.env.example
for this.Add a console.warn
to the 'magicEmailRegistration'
case in the
registerHandler()
in
app/features/user-authentication/user-authentication-actions.server.ts
:
const { email, issuer: did } =
await magicAdmin.users.getMetadataByToken(didToken);
console.warn('did', did);
Set up the database:
npm run prisma:setup
Start dev server:
npm run dev
Sign up for an account under /register
by signing up with Magic.
Grab the did
that you logged out in the previous step from the terminal in
which you ran npm run dev
and add it to your .env
file as SEED_USER_DID
.
Remove the console.warn
from the registerHandler()
.
Now you can add the remaining values to your .env
file, which are used by
the main seed script:
SEED_USER_DID
- The steps above outlined how to get this value. This value"prisma:seed"
script.SEED_SEED_USER_EMAIL
- The email of the user that will be seeded in the"prisma:seed"
script.SENTRY_DSN
- The DSN for your Sentry project. This value is optional.Lastly, stop your npm run dev
script and run
npm run prisma:reset-dev
, which wipes the database, seeds the database with lots of data and starts up the dev server again.
This starts your app in development mode, rebuilding assets on file changes.
You can run the app with MSW mocking requests to third party services by running:
npm run dev-with-mocks
mocking requests from both the client and the server, or
npm run dev-with-server-mocks
mocking only requests from the server.
Make sure you run npx msw init ./public
once before you run this command to
initialize the MSW service worker. It should create a file in
/public/mockServiceWorker.js
for you.
This is useful for developing offline or without hitting any API.
By default, MSW is used in the French House Stack to mock requests to Magic in
your E2E tests. Check out playwright/e2e/user-authentication/logout.spec.ts
and app/test/mocks/handlers/magic.ts
to see how to use MSW on the server.
"prisma:deploy"
- Applies all pending migrations from theprisma/migrations
directory to the database. This is typically used in a"prisma:migrate"
- Run via npm run prisma:migrate -- "my_migration_name"
prisma/migrations
directory based on"prisma:push"
- Applies changes from the Prisma schema to the database"prisma:reset-dev"
- Wipes the database, seeds it, and starts the"prisma:reset-dev-with-mocks"
- Wipes the database, seeds it, starts the"prisma:seed"
- Seeds the database with predefined data. This is useful for"prisma:setup"
- Generates Prisma Client, applies all pending migrations to"prisma:studio"
- Opens Prisma Studio, a visual interface for viewing and"prisma:wipe"
- Wipes the database, deleting all data but keeping theThis repository uses Plop to automate the generation of common boilerplate.
Run npm run gen
and then choose what you want to create, e.g.:
$ npm run gen
> gen
> plop
? What do you want to generate? React component
? For what feature do you want to generate the React component? user profile
? What is the name of the React component? user name
โ ++ /app/features/user-profile/user-name-component.tsx
โ ++ /app/features/user-profile/user-name-component.test.tsx
Out of the box, there are three options:
We're using flat routes, a feature which will ship natively with Remix, soon.
You can check out this video for an in-depth explanation.
The French House Stack uses Magic for authentication with
a custom session cookie. You can find the implementation in
app/features/user-authentication
.
Magic keeps track of the user's session in a cookie, but the FHS ignores Magic's session and uses a session cookie instead. This is because Magic's sessions only last 2 weeks, while the cookie lasts a year. Additionally, it makes E2E tests easier because you can fully control the auth flow.
After a user successfully authenticates via Magic, you create a unique session
in your system, tracked by UserAuthSession
. This session ID is then securely
stored in our session cookie, which we manage using
Remix's session utils.
The code for managing these sessions is located in
app/features/user-authentication/user-authentication-session.server.ts
.
The use of custom auth sessions enables you to to proactively invalidate sessions is necessary.
If the user is signing up, you also create a user profile for them using their email, which you can grab from Magic during the sign up flow.
When a user signs out, you delete the UserAuthSession
and clear the session
cookie.
ShadcnUI is configured in the "New York" setting, but it uses icons from
lucide-react
, so when generating a component you need to switch out that
import because the "New York" setting usually uses icons from
@radix-ui/react-icons
. lucide-react
is used because it has a wider selection
of icons.
In addition to the components from ShadcnUI, the French House Stack comes with some custom components:
app/components/disableable-link.tsx
- A link tag that can be disabled.app/components/drag-and-drop.tsx
- A drag and drop file input component.app/components/general-error-boundary.tsx
- An error boundary componentapp/components/sidebar.tsx
- A sidebar with header and burger menuuseMatches
on a per route basis. (Seeapp/features/organizations/organizations-sidebar-component.tsx
for anapp/components/text.tsx
- Various text components that can be used to renderThe French House Stack comes with localization support through remix-i18next.
The namespaces live in public/locales/
.
Remember to add new namespaces to app/features/localization/i18next.server.ts
to make them available in the server bundle and to app/test/i18n.ts
to make
sure they're available in the React component tests.
The French House Stack comes with error reporting using Sentry build in.
To use it, you need to set the SENTRY_DSN
environment variable. You can get
this value from your Sentry project.
If you want to configure source maps, look up how to do that in the Sentry docs.
The French House Stack includes utilities for toast notifications based on flash sessions.
Flash Data: Temporary session values, ideal for transferring data to the next request without persisting in the session.
Redirect with Toast:
redirectWithToast
(Path: app/utils/toast.server.ts
)return redirectWithToast(`/organizations/${newOrganizations.slug}/home`, {
title: 'Organization created',
description: 'Your organization has been created.',
});
ResponseInit
to set headers.Direct Toast Headers:
createToastHeaders
(Path: app/utils/toast.server.ts
)return json(
{ success: true },
{
headers: await createToastHeaders({
description: 'Organization updated',
type: 'success',
}),
},
);
Combining Multiple Headers:
combineHeaders
(Path: app/utils/toast.server.tsx
)return json(
{ success: true },
{
headers: combineHeaders(
await createToastHeaders({ title: 'Profile updated' }),
{ 'x-foo': 'bar' },
),
},
);
We use GitHub Actions for pull request checks. Any pull request triggers checks such as linting, type checks, unit tests and E2E tests.
Check out the Remix team's official stacks to learn how to use GitHub Actions for continuous integration and deployment.
Note: make sure you've run
npm run dev
at least one time before you run the E2E tests!
We use Playwright for our End-to-End tests in this project. You'll find those in
the playwright/
directory. As you make changes to your app, add to an existing
file or create a new file in the playwright/e2e
directory to test your
changes.
Playwright natively features testing library selectors for selecting elements on the page semantically.
To run these tests in development, run npm run test:e2e
which will start the
dev server for the app as well as the Playwright client.
Note: You might need to run
npx playwright install
to install the Playwright browsers before running your tests for the first time.
Some of the colors of ShadcnUI's components are lacking the necessary contrast.
You can deactivate those elements in checks like this:
const accessibilityScanResults = await new AxeBuilder({ page })
.disableRules('color-contrast')
.analyze();
// or
const accessibilityScanResults = await new AxeBuilder({ page })
.disableRules('color-contrast')
.analyze();
or pick a color scheme like "purple" that has good contrast.
If you're using VSCode, you can install the Playwright extension for a better developer experience.
We have a utility for testing authenticated features without having to go through the login flow:
test('something that requires an authenticated user', async ({ page }) => {
await loginByCookie({ page });
// ... your tests ...
});
Check out the playwright/utils.ts
file for other utility functions.
To mark a test as todo in Playwright,
you have to use .fixme()
.
test('something that should be done later', ({}, testInfo) => {
testInfo.fixme();
});
test.fixme('something that should be done later', async ({ page }) => {
// ...
});
test('something that should be done later', ({ page }) => {
test.fixme();
// ...
});
The version using testInfo.fixme()
is the "preferred" way and can be picked up
by the VSCode extension.
For lower level tests of utilities and individual components, we use vitest
.
We have DOM-specific assertion helpers via
@testing-library/jest-dom
.
By default, Vitest runs tests in the
"happy-dom"
environment. However,
test files that have .server
in the name will be run in the "node"
environment.
npm run test
- Runs all Vitest tests.npm run test:unit
- Runs all unit tests with Vitest. Your unit tests should.test.ts
or .test.tsx
.npm run test:integration
- Runs all integration tests Vitest. Your.spec.ts
or .spec.tsx
.npm run test:coverage
- Runs all Vitest tests and generates a coveragenpm run test:e2e
- Runs all E2E tests with Playwright.npm run test:e2e:ui
- Runs all E2E tests with Playwright in UI mode.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 type-check
.
This project uses ESLint for linting. That is configured in .eslintrc.cjs
.
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.
Remember to remove the MIT license and add your own license if you're building a commercial app.
The French House Stack comes with a SQLite database out of the box. It uses Prisma to abstract away the database layer, so you can easily switch it out for another database.
If you're looking for inspiration for a centralized database, check out the Blues Stack for a enterprise grade PostgeSQL setup.
If you build a DApp, you might want to use the IPFS or something like 3Box.
Magic is compatible with a variety of blockchains. The most popular for DApps is Ethereum and the most popular chain in general is Bitcoin.
Learn how you can deploy your Remix app here. For examples of setups you can check out the official Remix stacks.
The French House Stack comes with a magic link setup via email preconfigured. However, Magic also offers social auth (e.g. for Google), multi-factor auth and WebAuthn.
Note: the included cookie based authentication with
createCookieSessionStorage
is set up
as recommended by the Magic docs.
However, it doesn't work for Web3 functions. You'll need to
stay logged in with Magic
to work with any chain.
Here is a list of things this app could use:
Now go out there make some magic! ๐งโโ๏ธ