---
title: "Deploy a Stateful React App with Convex"
description: "Learn how to use Convex to transform a local React app into a fully-distributed dynamic application, deployed globally on Netlify, all in under 10 minutes."
source: "https://www.netlify.com/blog/state-management-with-convex/"
last_updated: "2026-07-14T06:13:10.000Z"
---
One of the biggest challenges when building a dynamic application is the management of global state: deploying a backend, interacting with a database, caching, and manually synchronizing data between client and server. Manual state management is not only complex, but any errors can easily result in an application displaying data that is stale, incorrect, or incomplete.

[Convex](https://convex.dev) is a global state management platform designed for the needs of dynamic application developers, obviating the need for complex error-prone application logic. Today we’re going to use Convex to transform a local React app into a fully-distributed dynamic application, deployed globally on [Netlify](https://netlify.com), all in under 10 minutes.

## Our local app

We’re going to start with a simplified Reddit clone with posts and updates.

![image](https://cdn.sanity.io/images/o0o2tn5x/production/db8dddfb0b1c9f006c6a39f28d6bc60f35cd9983-1204x578.png?w=450)

Clone and run the sample code for a basic local-only version of this app:

```
git clone https://github.com/get-convex/creddit.gitcd credditnpm installnpm run dev
```

Then load [localhost:3000](http://localhost:3000) in your browser to try adding and upvoting posts. It’s a bit of a stretch to call it a Reddit clone but we’re keeping things simple for now - additional features won’t fundamentally change the underlying implementation.

Now’s a good time to familiarize yourself with what this code is doing. The interesting code is in `pages/index.tsx`. If you’re a React or Next.js developer this should all look pretty familiar. The posts are stored in the `posts` state array and there are two functions that interact with it: `handleAddPost` which adds a new post and `handleUpvote` which increments the vote count for a post.

So far so good but the state in our app is all _local_. Refresh the page and the posts disappear. Open the page in a different browser tab and none of the posts will be there. Clearly this isn’t what we want.

## Making a local React app global

The problem with our current app is that the `posts` array is local state, whereas we want it to be _global_ state. This would normally be a big change. Get your stopwatch ready because we’re going to fix this in ten minutes.

### Convex Basics

Convex is a platform for global state management. It provides a backend database to store your state, allows you to query this state via server functions written in JavaScript or Typescript, and includes client libraries that synchronize state between client and server. Convex also provides the ability to _subscribe_ to the output of a server function and to automatically rerender browser components when the output of that function changes.

We’re going to jump straight in to using Convex but if you get lost along the way [the Convex docs](https://docs.convex.dev) are your friend.

### Pre-reqs

The first thing we’re going to need is a free account with Convex. You can sign up for the beta at [convex.dev](https://www.convex.dev/). You’ll also need a free [Netlify account](https://www.netlify.com/) which should take less than a minute to set up. Netlify is a cloud development platform that allows us to deploy our frontend code globally, straight from GitHub. Finally you’ll want to fork the `creddit` git repository so you can make local changes and push them to your own repository.

Once you’ve got the pre-requisites under control it’s time to start your stopwatch. Let’s do this.

### Install and initialize Convex

From the root of the `creddit` directory, add the Convex development libraries and Convex command line tool:

```
npm i convex-dev
```

Then, create a new global Convex instance:

```
npx convex init --beta-key <your beta key>
```

This tool will have added a `convex.json` file that includes your Convex config, a `.env.local` file with your access token, and a `convex/` directory to store your Convex server functions. Typically you’ll want to check `convex.json` into git but keep your `.env.local` file private.

### Global data model

We really just want our `posts` array to be global state stored in the cloud and to have the relevant changes synced locally. In Convex this is a pretty straightforward change.

Since we’ll be storing posts in Convex we’ll first change the `Post` type definition in `lib/common.ts` to use the Convex-assigned document ID as an identifier instead of the UUID we were using:

```
import { Id } from "convex-dev/values";
export type Post = {  _id: Id; // convex-assigned id  title: string;  date: number; // unix ms  votes: number;};
```

### Convex server functions

We also need some functions to interact with the global `posts` state. The first function we need is to create a new post. Add the following to `convex/addPost.ts`:

```
import { mutation } from "convex-dev/server";
// Add a post.export default mutation(({ db }, title: string) => {  console.log("posting", title);  db.insert("posts", { title, date: Date.now(), votes: 1 });});
```

This is a simple mutation function that inserts a new post into the `posts` table, with the given `title`, current `date` and a vote count of `1`.

We also need to be able to upvote a post, so let’s add a function for that, in `convex/upvote.ts`:

```
import { mutation } from "convex-dev/server";import { Id } from "convex-dev/values";import { Post } from "../lib/common";
// Upvote a post.export default mutation(async ({ db }, id: Id) => {  console.log("upvoting", id);  const post: Post | null = await db.get(id);  if (post === null) {    throw new Error(`No post with id ${id}`);  }  db.update(id, { votes: post.votes + 1 });});
```

This function takes a post id, fetches the existing post from the database, then writes an updated version with an incremented vote count.

Finally we need a way of querying all the current posts, which we add to `convex/listPosts.ts`:

```
import { query } from "convex-dev/server";import { Post } from "../lib/common";
// List all posts in sorted order.export default query(async ({ db }): Promise<Post[]> => {  console.log("listing posts");  const posts: Post[] = await db.table("posts").collect();  posts.sort((a, b) => b.votes - a.votes);  return posts;});
```

This is a simple query that performs a table scan followed by a sort of the posts in descending vote order.

There’s a small amount of boilerplate and syntax required in these functions but note what is _not_ required: the `listPosts` query just fetches the latest version of the posts - it doesn’t do any polling, doesn’t have any refresh logic, doesn’t deal with caching, etc. All of these are handled by some Convex magic we’ll get to shortly.

Now we have all the required query functionality you can run

```
npx convex codegen
```

to generate TypeScript stubs in `convex/_generated.ts` that will help when building the rest of our code.

### Connect the React App to Convex

We need to add a few lines of code to `pages/_app.tsx` to link our code with the Convex libraries. First add the following to the import block:

```
import { ConvexProvider, ConvexReactClient } from "convex-dev/react";import convexConfig from "../convex.json";
const convex = new ConvexReactClient(convexConfig.origin);
```

this imports the Convex libraries, reads the configuration file from `convex.json`, and initializes a connection to Convex via `ConvexReactClient`.

Next we want to wrap our top-level React component (`<Component {...pageProps} />`) in a `ConvexProvider` which will provide access to the `convex` client connection to all descendants in the component tree:

```
<ConvexProvider client={convex}>  <Component {...pageProps} /></ConvexProvider>
```

### Calling Convex functions

Our last step is to call the `addPost` and `upvote` functions to mutate `posts` state and to read from the `listPosts` function to render the latest posts.

The `useMutation` function provides a handle to call a Convex function on the server, for example:

```
const addPost = useMutation("addPost");...await addPost(newPostTitle);
```

Reading state from a Convex function is surprisingly streamlined:

```
const posts = useQuery("listPosts") ?? [];
```

The `useQuery` hook _binds_ the output of `listPosts` to a local array `posts` and updates it automatically whenever server data changes that affects the output of `listPosts`. In practice what this means is that any time a new post shows up from any client, our local React component will re-render with the new state.

The code in `pages/index.tsx` [actually gets _shorter_](https://github.com/get-convex/creddit/compare/main...convex?diff=unified#diff-aa98fd0757d0e1741503c50cfafb7726939d19819638bbe8e030a27adfec34a3) as a result of switching to Convex.

### Testing it out

Our work porting to Convex is done. Hopefully your code looks pretty similar to the [finished product](https://github.com/get-convex/creddit/tree/convex) we have in the `convex` branch of `https://github.com/get-convex/creddit.git`.

Push your server functions to Convex using:

```
npx convex push
```

and then run your app locally via:

```
npm run dev
```

The app should look identical to before except that the state you’re seeing is actually _global_, stored in the Convex cloud. If you open a second browser tab and start adding/upvoting posts you’ll see them dynamically update in both tabs!

## Deployment

Our application state is global but it’s hard to tell because the frontend is just running on our local host. Time to fix that by [deploying the frontend to Netlify](https://docs.convex.dev/getting-started/deployment/hosting/netlify).

Commit your code changes and push them to your code repository on GitHub, GitLab or BitBucket. Don’t forget to check in the `convex/` directory and the `convex.json` file.

Now go to [https://app.netlify.com/start](https://app.netlify.com/start) and link your repository with Netlify. The default options should work fine:

![image](https://cdn.sanity.io/images/o0o2tn5x/production/319d9229a11abf6d634f84004cec20b9343d5857-1052x1582.png?w=450)

Now click the Deploy Site button and we’re all set. Netlify will build your app, deploy it to a global CDN, and then give you a `https://<project-name>.netlify.app` URL where it can be accessed.

Time to hit _stop_ on your stopwatch. How did we do?

## Next Steps

That was a bit of a whirlwind introduction but it covered the basics of deploying a global dynamic app with Convex and Netlify.

There’s lots of functionality you might want to add to your app:

-   User accounts
-   Links and comments on posts
-   Better voting controls
-   Optimistic updates for faster reactivity
-   Image macros

Convex already supports these with authorization, foreign keys, complex business logic in server functions, optimistic updates, and Netlify external functions, but there are plenty more features on the way.

Take a look at [the Convex documentation](https://docs.convex.dev) to learn more about the platform and start building your next dynamic global application!

### Share

-   [X (fka Twitter)](https://twitter.com/intent/tweet?text=Global State Management on Netlify with Convex&url=https://www.netlify.com/blog/state-management-with-convex/)
-   [LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.netlify.com%2Fblog%2Fstate-management-with-convex%2F)
-   [Facebook](https://www.facebook.com/sharer.php?u=https://www.netlify.com/blog/state-management-with-convex/)
-   [Bluesky](https://bsky.app/intent/compose?text=Global State Management on Netlify with Convex+https://www.netlify.com/blog/state-management-with-convex/)

* * *

### Tags

-   [Databases](/blog/tags/databases/)
-   [stateful](/blog/tags/stateful/)
-   [Convex](/blog/tags/convex/)
-   [React](/blog/tags/react/)
-   [Reactive Programming](/blog/tags/reactive-Pprogramming/)

## Keep reading

![](/images/blog-fallback-thumbnail.svg)

Guides & Tutorials December 1, 2020

[

### Using React Context for state management in Next.js

](/blog/2020/12/01/using-react-context-for-state-management-in-next.js/)

-   ![Profile picture of Cassidy Williams](/_astro/a62099fab0f946e063c4b84ff4b4d9c94f9aa7a5-400x400_ZdakPa.webp)
    
    Cassidy Williams
    

![](/images/blog-fallback-thumbnail.svg)

Guides & Tutorials March 16, 2022

[

### Next.js on Netlify: a Powerful Combination

](/blog/next.js-on-netlify-features)

-   ![Profile picture of Charlotte Dillon](/_astro/ba092e80ad4e8fc41b238c66c3f472b477d1c53b-80x80_ZnCI2I.webp)
    
    Charlotte Dillon
    

## Recent posts

News & Announcements July 13, 2026

[

### Netlify inside of Claude Design

](/blog/netlify-inside-of-claude-design)

-   ![Profile picture of Sean Roberts](/_astro/bbf2243f8171dbddd80ab2103622106cef84d125-512x512_Z1d2LKE.webp)
    
    Sean Roberts
    

News & Announcements June 25, 2026

[

### Netlify Functions, designed for Agent Experience

](/blog/netlify-functions-designed-for-agent-experience)

-   ![Profile picture of Eduardo Bouças](/_astro/52958f21e8450baf6d8e60302341a984e220c0cd-512x512_13VDlu.webp)
    
    Eduardo Bouças
    

News & Announcements June 24, 2026

[

### How we measure Netlify’s Agent Experience

](/blog/how-we-measure-netlify-agent-experience)

-   ![Profile picture of Sean Roberts](/_astro/bbf2243f8171dbddd80ab2103622106cef84d125-512x512_Z1d2LKE.webp)
    
    Sean Roberts
    

![](/_astro/3f255b372fa958df35802666ee33b4609b2d71bd-1200x1586_1VtE2D.webp)

### How do the best dev and marketing teams work together?

[Access the report](https://www.netlify.com/reports/2024-leadership-trend-report/access/)