perf: cut cross-region latency (Smart Placement, session cookie cache, shared Autumn provider, parallel queries) (#359)

This commit is contained in:
Ben Senescu 2026-07-05 22:54:33 -04:00 committed by GitHub
parent c9bcc444b4
commit 828fb17073
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 91 additions and 101 deletions

View File

@ -1,10 +1,11 @@
GSC UI, improved app layout and beta in app agent.
GSC UI, improved app layout and beta in app agent.
## What's new
- Get GSC Insights inside the app — thanks @mattmacrocket
- See queries in "striking distance" of ranking
- See queries in "striking distance" of ranking
- (Beta) In app agent - MCP is still recommended, but we'll be working to improve this during the summer.
- Requires `OPENROUTER_API_KEY`
- Requires `OPENROUTER_API_KEY`
- Redesigned the app layout
## Fixed

View File

@ -1,4 +1,4 @@
import { AutumnProvider, useCustomer } from "autumn-js/react";
import { useCustomer } from "autumn-js/react";
import { AuditHistorySection } from "@/client/features/audit/launch/AuditHistorySection";
import { LaunchFormCard } from "@/client/features/audit/launch/LaunchFormCard";
import { useLaunchController } from "@/client/features/audit/launch/useLaunchController";
@ -18,11 +18,7 @@ export function LaunchView(props: LaunchViewProps) {
return <LaunchContent {...props} isFreePlan={false} />;
}
return (
<AutumnProvider>
<HostedLaunchView {...props} />
</AutumnProvider>
);
return <HostedLaunchView {...props} />;
}
function HostedLaunchView(props: LaunchViewProps) {

View File

@ -1,5 +1,5 @@
import { Link } from "@tanstack/react-router";
import { AutumnProvider, useCustomer } from "autumn-js/react";
import { useCustomer } from "autumn-js/react";
import { useSession } from "@/lib/auth-client";
import { getCustomerPlanStatus } from "@/client/features/billing/plan-detection";
import {
@ -12,14 +12,6 @@ import {
} from "@/shared/billing";
export function FreePlanBanner() {
return (
<AutumnProvider>
<FreePlanBannerContent />
</AutumnProvider>
);
}
function FreePlanBannerContent() {
const { data: session } = useSession();
const customerQuery = useCustomer({
queryOptions: {

View File

@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { AutumnProvider, useCustomer } from "autumn-js/react";
import { useCustomer } from "autumn-js/react";
import { useSession } from "@/lib/auth-client";
import { isHostedClientAuthMode } from "@/lib/auth-mode";
import { getCustomerPlanStatus } from "@/client/features/billing/plan-detection";
@ -23,11 +23,7 @@ export function HostedPlanGate({
return children(SELF_HOSTED_PLAN_GATE);
}
return (
<AutumnProvider>
<HostedPlanGateContent>{children}</HostedPlanGateContent>
</AutumnProvider>
);
return <HostedPlanGateContent>{children}</HostedPlanGateContent>;
}
function HostedPlanGateContent({

View File

@ -1,5 +1,4 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import { AutumnProvider } from "autumn-js/react";
import { useState } from "react";
import { Loader2 } from "lucide-react";
import { DEFAULT_LOCATION_CODE } from "@/shared/keyword-locations";
@ -57,9 +56,7 @@ export function OnboardingChat() {
{!domain ? (
<SiteForm projectId={projectId} />
) : (
<AutumnProvider>
<OnboardingChatConversation projectId={projectId} domain={domain} />
</AutumnProvider>
<OnboardingChatConversation projectId={projectId} domain={domain} />
)}
</StrategyShell>
);

View File

@ -1,7 +1,7 @@
import { useMemo, useState } from "react";
import { toast } from "sonner";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { AutumnProvider, useCustomer } from "autumn-js/react";
import { useCustomer } from "autumn-js/react";
import {
getLatestRankResults,
getRankPositionMatrix,
@ -57,20 +57,7 @@ function deviceVisibility(
};
}
export function RankTrackingDomainDetail(props: {
config: RankTrackingConfig;
projectId: string;
onBack: () => void;
onEdit: () => void;
}) {
return (
<AutumnProvider>
<RankTrackingDomainDetailInner {...props} />
</AutumnProvider>
);
}
function RankTrackingDomainDetailInner({
export function RankTrackingDomainDetail({
config,
projectId,
onBack,

View File

@ -20,4 +20,17 @@ export const baseAuthOptions = {
user: {
additionalFields: userAdditionalFields,
},
session: {
// Serve getSession from a signed cookie instead of a DB round trip. The
// session lookup runs on every authenticated request, and the DB lives in
// us-east — from far colos that single query was ~1s of wall time. The
// trade-off is revocation lag: a session revoked elsewhere (sign-out on
// another device, password reset) stays valid on an already-issued cookie
// for up to maxAge. Authorization still hits the DB via the canonical
// project-access checks, so the cookie only vouches for identity.
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
};

View File

@ -9,6 +9,7 @@ import {
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
import { TanStackDevtools } from "@tanstack/react-devtools";
import { QueryClientProvider } from "@tanstack/react-query";
import { AutumnProvider } from "autumn-js/react";
import * as React from "react";
import { DefaultCatchBoundary } from "@/client/components/DefaultCatchBoundary";
import { ExportToSheetsModal } from "@/client/components/table/ExportToSheetsModal";
@ -173,27 +174,37 @@ function RootDocument({ children }: { children: React.ReactNode }) {
</head>
<body>
<ClientOnly>
<QueryClientProvider client={queryClient}>
<>
<PostHogBootstrap />
{children}
<ExportToSheetsModal />
<Toaster position="bottom-right" mobileOffset={{ bottom: 100 }} />
{showDevtools ? (
<TanStackDevtools
config={{ position: "bottom-right" }}
eventBusConfig={{ connectToServerBus: true }}
plugins={[
{
name: "TanStack Router",
render: <TanStackRouterDevtoolsPanel />,
defaultOpen: true,
},
]}
{/* Keep this the ONLY AutumnProvider. Each provider creates its own
customer cache (autumn-js bundles a private react-query, so it
never shares the app QueryClient), and every extra provider mount
pays its own ~1s getOrCreateCustomer round trip. It only provides
context nothing fetches until a useCustomer consumer mounts. */}
<AutumnProvider>
<QueryClientProvider client={queryClient}>
<>
<PostHogBootstrap />
{children}
<ExportToSheetsModal />
<Toaster
position="bottom-right"
mobileOffset={{ bottom: 100 }}
/>
) : null}
</>
</QueryClientProvider>
{showDevtools ? (
<TanStackDevtools
config={{ position: "bottom-right" }}
eventBusConfig={{ connectToServerBus: true }}
plugins={[
{
name: "TanStack Router",
render: <TanStackRouterDevtoolsPanel />,
defaultOpen: true,
},
]}
/>
) : null}
</>
</QueryClientProvider>
</AutumnProvider>
</ClientOnly>
<Scripts />
</body>

View File

@ -1,5 +1,5 @@
import { createFileRoute, notFound } from "@tanstack/react-router";
import { AutumnProvider, useCustomer } from "autumn-js/react";
import { useCustomer } from "autumn-js/react";
import { useEffect, useState } from "react";
import { useSession } from "@/lib/auth-client";
import { isHostedClientAuthMode } from "@/lib/auth-mode";
@ -31,14 +31,6 @@ export const Route = createFileRoute("/_app/billing")({
});
function BillingPage() {
return (
<AutumnProvider>
<BillingPageContent />
</AutumnProvider>
);
}
function BillingPageContent() {
const { data: session, isPending: isSessionPending } = useSession();
const [topUpAmount, setTopUpAmount] = useState("20");
const [isPending, setIsPending] = useState(false);

View File

@ -1,5 +1,5 @@
import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
import { AutumnProvider, useCustomer } from "autumn-js/react";
import { useCustomer } from "autumn-js/react";
import { useEffect, useState } from "react";
import { ArrowRight, Settings, User } from "lucide-react";
import { ThemePreferenceMenuItems } from "@/client/components/ThemePreferenceMenuItems";
@ -41,14 +41,6 @@ export const Route = createFileRoute("/_authenticated/subscribe")({
});
function SubscribePage() {
return (
<AutumnProvider>
<SubscribePageContent />
</AutumnProvider>
);
}
function SubscribePageContent() {
const navigate = useNavigate();
const { upgrade: isUpgradeFlow, redirect } = Route.useSearch();
const { data: session } = useSession();

View File

@ -269,8 +269,10 @@ async function refreshKeywordMetrics(
projectId: string,
billingCustomer: BillingCustomerContext,
): Promise<{ updated: number }> {
const config = await getValidatedConfig(configId, projectId);
const keywords = await RankTrackingRepository.getKeywordsForConfig(configId);
const [config, keywords] = await Promise.all([
getValidatedConfig(configId, projectId),
RankTrackingRepository.getKeywordsForConfig(configId),
]);
if (keywords.length === 0) return { updated: 0 };
const client = createDataforseoClient(billingCustomer);

View File

@ -26,29 +26,28 @@ export async function getLatestResults(
rows: RankTrackingRow[];
run: { id: string; lastCheckedAt: string } | null;
}> {
const config = await RankTrackingRepository.getConfigById({
configId,
projectId,
});
if (!config) {
throw new AppError("INTERNAL_ERROR", "Rank tracking config not found");
}
const activeKeywords =
await RankTrackingRepository.getKeywordsForConfig(configId);
// Get the latest snapshot per keyword per device (across all completed runs)
const currentSnapshots =
await RankTrackingRepository.getLatestSnapshotsForKeywords(configId);
// Get comparison snapshots from before the target date
const days = PERIOD_DAYS[comparePeriod];
const targetDate = toSqliteTimestamp(
new Date(Date.now() - days * 24 * 60 * 60 * 1000),
);
const comparisonSnapshots =
await RankTrackingRepository.getSnapshotsBeforeDate(configId, targetDate);
// All four reads key off the inputs alone, so run them in one parallel round
// trip instead of four sequential ones — this endpoint is hot and the DB may
// be a continent away. The project-scoped config lookup doubles as the
// authorization gate for the configId-keyed reads racing alongside it: when
// config is null, throw without returning anything from the other reads.
const [config, activeKeywords, currentSnapshots, comparisonSnapshots] =
await Promise.all([
RankTrackingRepository.getConfigById({ configId, projectId }),
RankTrackingRepository.getKeywordsForConfig(configId),
// Latest snapshot per keyword per device (across all completed runs)
RankTrackingRepository.getLatestSnapshotsForKeywords(configId),
// Comparison snapshots from before the target date
RankTrackingRepository.getSnapshotsBeforeDate(configId, targetDate),
]);
if (!config) {
throw new AppError("INTERNAL_ERROR", "Rank tracking config not found");
}
const previousPositions = new Map<string, number | null>();
for (const snap of comparisonSnapshots) {

View File

@ -6,6 +6,18 @@
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"observability": {
"enabled": true,
// Spans are billed as observability events (free tier 200k/day); add a
// traces head_sampling_rate below 1 if span volume becomes a cost concern.
"traces": {
"enabled": true,
},
},
// Smart Placement relocates only the `fetch` handler (cron + Workflow runs
// stay at the edge) toward external subrequests Postgres via Hyperdrive,
// SEO APIs not D1/KV/R2 bindings, so it's a DB no-op on the D1 default.
// Must live here: a dashboard-only toggle is wiped by `wrangler deploy`.
"placement": {
"mode": "smart",
},
// Uncomment when deploying to the openseo.so Cloudflare account. Self-hosters
// should leave this commented (or replace with their own zone) otherwise