Skip to content

enhancement(convex/nextjs): add better support for using preloadQuery with authenticated requests #69

@WestBrian

Description

@WestBrian

Enhancement request

It would be nice to have some better support for using the preloadQuery to fetch authenticated requests.

Problem

The issue arises when fetching authenticated data (using Clerk in this case) on the server and passing it to the client. The usePreloadedQuery hook subscribes to the data before the authentication check completes, causing the call to fail. As noted in the docs, "Make sure that the component calling this query is a child of <Authenticated> from convex/react. Otherwise, it will throw on page load." However, this requirement defeats the purpose of preloading, since it forces the UI to wait for client-side authentication before rendering.

Current workarounds

Option 1

Here is a wrapper component I made that will display the preloaded query while the client authenticates and then use the hook once authentication completes.

'use client'

import {
  type Preloaded,
  Authenticated,
  AuthLoading,
  usePreloadedQuery,
} from "convex/react";
import type { FunctionReference } from "convex/server";
import { preloadedQueryResult } from "convex/nextjs";

interface PreloadedWrapperProps<T extends FunctionReference<"query">> {
  preloadedData: Preloaded<T>;
  render: (data: ReturnType<typeof preloadedQueryResult<T>>) => React.ReactNode;
}

const AuthenticatedRender = <T extends FunctionReference<"query">>(
  props: PreloadedWrapperProps<T>
) => {
  const authenticatedData = usePreloadedQuery(props.preloadedData);

  return <>{props.render(authenticatedData)}</>;
};

export const PreloadedWrapper = <T extends FunctionReference<"query">>(
  props: PreloadedWrapperProps<T>
) => {
  const preloadedData = preloadedQueryResult(props.preloadedData);

  return (
    <>
      <AuthLoading>{props.render(preloadedData)}</AuthLoading>
      <Authenticated>
        <AuthenticatedRender {...props} />
      </Authenticated>
    </>
  );
};

Usage looks like this:

<PreloadedWrapper preloadedData={props.preloadedTasks} render={(tasks) => (
  <div>...</div>
)} />

This comes with some drawbacks though like not being able to interweave server and client components.

Option 2

Custom usePreloadQueryWithAuth. Here is the implementation for quick reference:

import { preloadedQueryResult } from 'convex/nextjs';
import { Preloaded, usePreloadedQuery } from 'convex/react';
import { FunctionReference } from 'convex/server';

// Returns the result from server while the client
// is being authenticated.
// This assumes:
//  1. The query only returns `null` when it's waiting for auth
//  2. The query is always authenticated when called from the server
export function usePreloadedQueryWithAuth<
  Query extends FunctionReference<'query'>,
>(preloadedQuery: Preloaded<Query>): Exclude<Query['_returnType'], null> {
  const loaded = usePreloadedQuery(preloadedQuery);
  return loaded ?? preloadedQueryResult(preloadedQuery);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions