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 ## What's new
- Get GSC Insights inside the app — thanks @mattmacrocket - 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. - (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 - Redesigned the app layout
## Fixed ## 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 { AuditHistorySection } from "@/client/features/audit/launch/AuditHistorySection";
import { LaunchFormCard } from "@/client/features/audit/launch/LaunchFormCard"; import { LaunchFormCard } from "@/client/features/audit/launch/LaunchFormCard";
import { useLaunchController } from "@/client/features/audit/launch/useLaunchController"; import { useLaunchController } from "@/client/features/audit/launch/useLaunchController";
@ -18,11 +18,7 @@ export function LaunchView(props: LaunchViewProps) {
return <LaunchContent {...props} isFreePlan={false} />; return <LaunchContent {...props} isFreePlan={false} />;
} }
return ( return <HostedLaunchView {...props} />;
<AutumnProvider>
<HostedLaunchView {...props} />
</AutumnProvider>
);
} }
function HostedLaunchView(props: LaunchViewProps) { function HostedLaunchView(props: LaunchViewProps) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,29 +26,28 @@ export async function getLatestResults(
rows: RankTrackingRow[]; rows: RankTrackingRow[];
run: { id: string; lastCheckedAt: string } | null; 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 days = PERIOD_DAYS[comparePeriod];
const targetDate = toSqliteTimestamp( const targetDate = toSqliteTimestamp(
new Date(Date.now() - days * 24 * 60 * 60 * 1000), new Date(Date.now() - days * 24 * 60 * 60 * 1000),
); );
const comparisonSnapshots = // All four reads key off the inputs alone, so run them in one parallel round
await RankTrackingRepository.getSnapshotsBeforeDate(configId, targetDate); // 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>(); const previousPositions = new Map<string, number | null>();
for (const snap of comparisonSnapshots) { for (const snap of comparisonSnapshots) {

View File

@ -6,6 +6,18 @@
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"observability": { "observability": {
"enabled": true, "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 // Uncomment when deploying to the openseo.so Cloudflare account. Self-hosters
// should leave this commented (or replace with their own zone) otherwise // should leave this commented (or replace with their own zone) otherwise