惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
阮一峰的网络日志
阮一峰的网络日志
S
Secure Thoughts
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
P
Privacy & Cybersecurity Law Blog
The Hacker News
The Hacker News
H
Heimdal Security Blog
W
WeLiveSecurity
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
The Last Watchdog
The Last Watchdog
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
D
DataBreaches.Net
I
Intezer
Webroot Blog
Webroot Blog
C
Cisco Blogs
AWS News Blog
AWS News Blog
博客园 - 聂微东
T
The Blog of Author Tim Ferriss
V
Vulnerabilities – Threatpost
罗磊的独立博客
Google DeepMind News
Google DeepMind News
N
Netflix TechBlog - Medium
Schneier on Security
Schneier on Security
宝玉的分享
宝玉的分享
博客园 - 叶小钗
PCI Perspectives
PCI Perspectives
D
Docker
Scott Helme
Scott Helme
NISL@THU
NISL@THU
J
Java Code Geeks
B
Blog RSS Feed
Google Online Security Blog
Google Online Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Exploit Database - CXSecurity.com
AI
AI
美团技术团队
Cloudbric
Cloudbric
月光博客
月光博客
P
Proofpoint News Feed
T
Tailwind CSS Blog
Google DeepMind News
Google DeepMind News
小众软件
小众软件
www.infosecurity-magazine.com
www.infosecurity-magazine.com
The Cloudflare Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

Clerk Blog

Going to production with Clerk Deploy Clerk Init: The fastest way to start a new project Introducing Clerk CLI Middleware-based route protection bypass Postmortem: Clerk System Outage (March 10, 2026) Clerk for the AI era Add API Key support to your SaaS in minutes Postmortem: Clerk System Outage (February 19, 2026) Using Clerk in a React Native app Postmortem: DNS Provider Outage (February 10, 2026) How do I implement passkeys in Next.js? Clerk ranked #4 fastest-growing software vendor on Ramp’s December 2025 list How do I handle JWT verification in Next.js? Committing to Agent Identity: Clerk raises $50m Series C from Menlo and Anthropic’s Anthology Fund What is the best way to handle authentication in Next.js App Router? Postmortem: Database Incident (September 14–18, 2025) How do I add authentication to a Next.js app? Introducing Free Trials in Clerk Billing Postmortem: August 28, 2025 - elevated API latency and errors Introducing Mosaic: Bring Your Brand to Every Authentication Flow Multi-tenant authentication: What you need to know (and how Clerk helps) What are the risks and challenges of multi-tenancy? Resilience in Practice: Regional Failover at Clerk Build a Cross-Platform B2B App with Clerk, Expo, and Supabase Highlights from the MiduDev/Clerk Hackathon Add multi-tenancy to an app built with Clerk, Lovable, and Supabase How to build an AI coding rules app with Clerk, Lovable, and Supabase How to Build Multi-Tenant Authentication with Clerk Choosing the right SaaS architecture: Multi-Tenant vs. Single-Tenant Postmortem: June 26, 2025 service outage How to Design a Multi-Tenant SaaS Architecture What is multi-tenancy and why it matters for B2B SaaS How OAuth Works Synchronize user data from Clerk to Supabase Add subscriptions to your SaaS with Clerk Billing Getting started with Clerk Billing Multi-tenant analytics with Tinybird and Clerk How Huntr Migrated 250K Users to Clerk: A Scalable Auth Solution for Startups How to take Clerk to Production How to take your Clerk application to production A practical guide to testing Clerk Next.js applications Implementing multi-tenancy into a Supabase app with Clerk How Clerk integrates with a Next.js application using Supabase How Clerk integrates with Supabase Build a blog with tRPC, Prisma, Next.js and Clerk How to enrich PostHog events with Clerk user data How to build a secure project management platform with Next.js, Clerk, and Neon Validate your SaaS idea while building an audience Postmortem: February 6, 2025 service outage Implement Role-Based Access Control in Next.js 15 Build a Next.js sign-up form with React Hook Form Build a Next.js login page template How to implement Google authentication in Next.js 15 What is middleware in Next.js? How to customize Next.js metadata How to set environment variables in Node.js Building a React Login Page Template How to implement per-user OAuth scopes with Clerk Using Clerk SSO to access Google Calendar and other service data Streamline enterprise customer onboarding with SAML and Clerk Clerk launches EASIE SSO and eliminates SSO fees How to secure Liveblocks Rooms with Clerk in Next.js Securing Node.js Express APIs with Clerk and React Combining the benefits of session tokens and JWTs Build a task manager with Next.js, Supabase, and Clerk Comparing Clerk Webhooks vs Backend API Automate Neon schema changes with Drizzle and GitHub Actions A guide to reading authenticated user data from Clerk Role based access control with Clerk Organizations Mitigating OAuth’s recently discovered Open Response Type vulnerability Per-user B2B monetization with Stripe and Clerk Organizations Build a team-based task manager with Next.js, Neon, and Clerk Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements Welcoming Colin from Zod as our inaugural Open Source Fellow Build a modern authenticated chat application with Next.js, Ably, and Clerk Build a waitlist with Clerk user metadata How to use Clerk with PostHog Identify in Next.js How to secure API Gateway using JWT and Lambda Authorizers with Clerk What are passkeys and how do they work? Comparing Authentication in React.js vs. Next.js How to Add an Onboarding Flow for your Application with Clerk Create Your Own Custom User Menu with Radix - Part 2 Introducing Webhook Workflows with Inngest & Svix Clerk raises $30M Series B from CRV and Stripe Clerk in 2023: A Year in Review Ultimate Guide to Magic Link Authentication Create Your Own Custom User Menu with Radix Introducing has(), protect(), and <Protect> Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan” Next.js Authentication with Clerk: Streamlined SSR Handling Clerk Webhooks: Data Sync with Convex Exploring Clerk Metadata with Stripe Webhooks The Ultimate Guide to Next.js Authentication Empower Your Support Team With User Impersonation Clerk Webhooks: Getting Started A Complete Guide to Session Management in Next.js The Advanced Guide to Passwordless Authentication in Next.js How We Roll – Chapter 10: Roundup How We Roll – Chapter 9: Infrastructure Password-Based Authentication in Next.js
Build a Movie Emoji Quiz App with Remix, Fauna, and Clerk
Ian McPhail · 2024-01-03 · via Clerk Blog

Project setup

A fun challenge coming out of the modern smartphone world is to identify a movie based only on a sequence of emoji. Sometimes the emoji “spell out” the words in the title, while other times they identify key plot themes. Test the emoji game of all the movie buffs you know by building a Movie Emoji Quiz app.

In this tutorial, we will build the app using the Remix full-stack web framework, a Fauna database, and Clerk for authentication and user management. Remix is a relatively new, open-source framework for React that has been gaining traction. Fauna is a developer-friendly, cloud-based database platform that offers modern features, such as real-time streaming and GraphQL. Clerk, an authentication provider built for the modern web, has a first-party Remix authentication package and integrates with Fauna through its JWT templates feature.

A brief web search didn't come up with any existing Remix and Fauna tutorials. And then I found this tweet from Ryan Florence (@ryanflorence), the co-founder of Remix:

I really want to build some demos with FaunaDB (I love the direction they're going) and AWS DynamoDB (I mean, come on, it's solid) as well. — Ryan Florence (@ryanflorence) April 6, 2021

That sold me on moving forward in building with this stack.

Assumptions

This tutorial makes the following assumptions...

  • Basic command line usage
  • Node.js (≥ v14) installed with npm (≥ v7)
  • Experience with React components and hooks
  • Clerk and Fauna accounts already set up (if you haven’t done so, do it now... we’ll wait)

Set up a Clerk application

The first step is to create a new application from the Clerk dashboard. We’ll name this application Movie Emoji Quiz and leave the default Password authentication strategy selected. We’re also going to choose Google and Twitter as the social login providers, but feel free to select whichever ones you and your friends use.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

Click the Add application button and your Clerk development instance will be created.

Create Remix project

The next step is to create the Remix project. Run the following command in your terminal:

It may prompt you to install the create-remix package. Then respond with the following:

It will then ask “TypeScript or JavaScript?”, we’re going with JavaScript on this one, but that’s up to your personal preference.

As the instructions state, change into the app directory with cd movie-emoji-quiz/

Now install the two dependencies we need for this application:

Next, touch .env to create an environment variables file and replace <YOUR_FRONTEND_API> and <YOUR_CLERK_API_KEY> with their respective values from your Clerk instance, which you can get from the API keys page.

.env

Once those environment variables are set, spin up the Remix dev server:

Set up Clerk authentication

To share authentication state with Remix routes, we need to make three modifications to the app/root.jsx file:

  1. Export rootAuthLoader as loader
  2. Export ClerkCatchBoundary as CatchBoundary
  3. Wrap the default export with ClerkApp
app/root.jsx

Clerk supports Remix SSR out-of-the-box.

Set up Fauna database

From the Fauna dashboard, create a new database. We named ours emoji-movie-quiz and chose the Classic Region Group.

We only need to create one Collection for this application. You can do so either in the Dashboard UI or in the interactive query shell.

To do it in the shell, type the following:

Then press the Run query button. If it was successful, you should see similar output.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

We’re going to seed the database with a few examples to get started. If you’ve never used the Fauna Query Language (FQL) before, the syntax may look a little funny.

This will map over the examples array and create a new document with challenge data for each item in the Challenge collection.

You can navigate to the Collections tab to validate that the data is all set. You can even make edits directly from the user interface if you prefer.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

While we’re here, let’s navigate over to the Functions tab and write a couple FQL functions we will need later.

Click the New Function button, name it getChallenges, and then paste the following function body:

This function will get all the challenges and include the unique document ID inside each data object.

You can test out the functionality in the Shell by running:

The second function we’re going to define is called getChallengeById and will take the unique document ID parameter and return the respective challenge data or null if it doesn’t exist.

That’s all of the custom functions we’ll need here.

Authentication integration

Although Fauna offers built-in identity and basic password authentication, it requires that you manage the user data yourself and does not provide features like prebuilt UI components, <UserProfile /> access, and other auth strategies such as OAuth social login and magic links. Clerk provides these features and more without the hassle of managing your own user and identity service.

The Clerk integration with Fauna enables you to authenticate queries to your Fauna database using a JSON Web Token (JWT) created with a JWT template.

From your Clerk dashboard, navigate to the JWT Templates screen. Click the New template button and choose the Fauna template.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

Take note of the default template name of fauna (as this will come up later). You can leave the default settings and optionally add your own custom claims using convenient shortcodes.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

While keeping this tab open, go back to the Fauna dashboard and navigate to the Security page.

Click on the Roles tab and then create a New Custom Role. Name this role user and give it Read and Create access to the challenge collection as well as Call permission for both getChallenges and getChallengeById functions.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

If everything looks correct, click Save to create the user role.

Next, click on the Providers tab and click on the New Access Provider button.

Enter Clerk as the name to identify this access provider.

We’re going to play a little pattycake back-and-forth between Fauna and Clerk, but bear with me and we’ll get through it together.

  1. Fauna: Copy the Audience URL and go back to the Clerk JWT template tab.
  2. Clerk: Paste the Audience URL as the value for the aud claim.
  3. Clerk: Copy the Issuer URL.
  4. Fauna: Paste into Issuer field.
  5. Clerk: Copy the JWKS Endpoint URL.
  6. Fauna: Paste into the JWKS endpoint field.
  7. Fauna: Select the user role for access.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

After those fields have been set, you can save both the Clerk JWT template and Fauna access provider. Whew! That was fun.

Add Clerk auth to Remix

Now we can get back into building our application. Open the Remix project in your code editor of choice.

In Remix, app/root.jsx wraps your entire application in both server and browser contexts.

Clerk requires a few modifications to this file so the authentication state can be shared with your Remix routes. First, add these imports to app/root.jsx:

Second, export rootAuthLoader as loader taking in the args parameter.

Next, we need to export the ClerkCatchBoundary as CatchBoundary to handle expired authentication tokens. If you want your own custom boundary, you can pass it in as the first argument.

And finally, we need to wrap the default export with the ClerkApp higher-order component.

That’s all that’s needed to install and configure Clerk for authentication. The next step will include adding sign in functionality.

Add SignIn to index route

We’re going to use the Clerk hosted <SignIn /> component to render the sign in form.

Update app/routes/index.jsx with the following:

app/routes/index.jsx

We’re using Remix’s route styles functionality to dynamically add a stylesheet to this route.

Create a styles directory inside of the app folder and save the following as index.css:

app/styles/index.css

Your app should now look something like:

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

If you see a blank screen, you may be already signed in. We will handle that case momentarily.

Inside of the app directory, create a folder called components and a file called header.jsx.

Drop in the following code:

app/components/header.jsx

Here we’re making use of the <SignedIn> control flow component and the <UserButton /> which will allow us to edit our profile and sign out.

Go back to app/routes/index.jsx and import the <Header /> component and add it just above the <main> element:

app/routes/index.jsx

If you sign in, you should now see your avatar. Click on it to see the user profile menu.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

You can now sign in, sign out, and manage your account. Clerk makes it super easy with their hosted components.

Authenticate with the Fauna client

In order to start fetching data from our Fauna database, we need to set up the Fauna client.

Create a utils folder inside of app and a file named db.server.js.

Add the following code:

app/utils/db.server.js

Here we are using the getAuth function from Clerk to check if we have a userId (e.g. if the user is signed in) and to get access to the getToken function, which when called with our Fauna JWT template name (it was simply “fauna” if you remember), will be passed to the Fauna client as the authentication secret.

If the user is signed in and we get access to the Fauna JWT template, we should then be able to make queries against our Fauna database.

We are also exporting q here, which is a convention when using faunadb.query. This way all our database helper functions are kept in the same place.

Display movie challenges

Now it’s time to display some of these challenges.

First let’s create a new route at /challenges. We do this by creating a file app/routes/challenges.jsx and populating it with the following:

app/routes/challenges.jsx

This should look the same as the index route, with the exception being <SignIn /> is replaced with the Remix <Outlet /> component.

Next, import the database utils we previous created and export a loader function with the below code:

You can see we’re using the Fauna client to perform a FQL query to call the getChallenges function we created. If all went well, check your terminal window (not the browser console) and you should see the response data.

Create a new file app/components/sidebar.jsx that will loop over this data and create links to each challenge based on its ID.

app/components/sidebar.jsx

Import the <Sidebar /> component in the index route directly under the main element.

To access the data from the loader, we will use the aptly named useLoaderData hook from Remix and pass the data property to the Sidebar component:

If you visit http://localhost:3000/challenges, you should now see the emoji challenges rendered as links in the sidebar:

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

If you click on the links, they will cause an error because we haven’t created those individual challenge routes. Let’s do that now.

Create challenge route

Let’s create a new stylesheet that will hold the challenge styles at app/styles/challenge.css.

app/styles/index.css

Next, create a file inside a folder at the path app/routes/challenges/$id.jsx

The $ prefix is important here as it creates a dynamic segment.

Add the following code:

app/routes/challenges/$id.jsx

This code follows a similar pattern to what we did in the sidebar with the loader and the useLoaderData hook. The difference here is we’re calling the getChallengeById FQL function and passing it the id parameter, which comes from the $id dynamic route.

We are also using a Remix convention of throwing Response objects for error scenarios (e.g. invalid ID, challenge not found).

If you click on the links in the sidebar, you should now see each emoji challenge rendered in its full glory (a large type size).

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

Challenge form

Now it’s time to add the form to submit guesses for the emoji challenges. Form handling is one area where Remix really shines.

Add the following form markup below the emoji:

This is pretty standard JSX form markup. Nothing too fancy going on here.

Let’s import our DB utils as well as the json Response helper from Remix.

Then export the following action function:

Here we’re using native FormData methods to read the guess input and comparing it against the challenge title we get by calling our FQL getChallengeById function with the challenge ID from the route params.

Based on whether the guess matches the title (case insensitive), we return an appropriate JSON response. We can access the action data using the useActionData hook (also aptly named).

Now we can update the UI to display the correct message based on the guess submitted.

If an incorrect guess is submit, we provide the user a way to reveal the answer.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

The form should now work to submit correct and incorrect guesses.

Handling transitions

Because the form is shared between different challenge routes, if you enter text in one input and go to another challenge, you will see the input value is not being cleared.

We can fix this by using the useTransition hook, putting a ref on the form element, and then resetting the form when the transition is a normal page load.

You will need to add the necessary hook imports from React and Remix.

After that is added, the form should clear when choosing a different challenge.

Submit new challenges

This app is not much so much fun if users can’t submit their own movie emoji challenges. So that’s the functionality we’re going to add now.

As with most things in Remix, the first thing we need to do is create a new route.

Create app/routes/challenges/new.jsx with the following:

app/routes/challenges/new.jsx

This time we’re using the Form component provided by Remix, which helps with automatically serializing the values. There’s validation logic to check for valid emoji and titles. If the validation passes, we create a new challenge. We use the getAuth function from Clerk to send in the user ID along with the emoji and title. These values form the data for a new challenge document in Fauna. On a successful document creation, the user is redirected to the new challenge page.

Let’s add a link to the header so we can get to this page. It’s wrapped with <div className="actions"> to provide the necessary styling.

app/components/header.jsx

Clicking the link should take you to the page where you can create a new challenge. You should see validation errors if you don’t fill out the form properly.

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

Once submitted, you will be redirected to the new challenge page. Because the action created a new mutation, Remix will automatically update the data in the sidebar. Neat!

Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration

You now have a working Movie Emoji Quiz app. If you’re ready to share it with your family and friends, Remix makes deployment very easy and has support for various deployment targets. Using the Vercel CLI, all you need to deploy your Remix app is run:

And your app will be live within minutes!

Next steps

To take this app even further, you can do the following:

  • Add catch boundaries and redirects to handle error cases and invalid challenge routes
  • Create Remix routes for /sign-in and /sign-out to use mounted Clerk components
  • Use the Clerk User API to query the username for each user user-submitted challenge so you can give them credit for their cleverness
  • Keep track of correct guesses and display an indicator in the UI so the user knows which challenges they have yet to tackle

If you enjoyed this tutorial or have any questions, feel free to reach out to me (@devchampian) on X, follow @clerk, or join our Discord community to connect with other developers. Happy coding!