Skip to content

Commit 141159e

Browse files
committed
fix(dashboard): begin caching calls and moving reused functions to server actions
1 parent 45fb737 commit 141159e

File tree

14 files changed

+294
-217
lines changed

14 files changed

+294
-217
lines changed
Lines changed: 32 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import "server-only";
22

33
import { notFound } from "next/navigation";
4-
5-
import getGithubSourceMetadataHandler from "@/app/api/get-github-source-metadata/handler";
4+
import { Suspense } from "react";
5+
import { getGitHubAuthState } from "@/app/actions/getGithubMetadata";
66
import type { Auth0OrgName } from "@/app/services/auth0/types";
77
import getDocsSitesForOrg from "@/app/services/dal/fdr/getDocsSitesForOrg";
8-
import getDocsGithubUrl from "@/app/services/dal/github/getDocsGithubUrl";
9-
import { validateGithubRepoAccess } from "@/app/services/dal/github/validators";
8+
import { getDocsGithubUrl } from "@/app/services/dal/github/getDocsGithubUrl";
109
import { getAuthenticatedSessionOrRedirect } from "@/app/services/dal/organization";
1110
import { DocsSiteOverviewCard } from "@/components/docs-page/DocsSiteOverviewCard";
12-
import { FernCliVersionDisplay } from "@/components/docs-page/FernCliVersionDisplay";
13-
import { type GithubAuthState, GithubSource } from "@/components/docs-page/GithubSource";
14-
import { VisualEditorSection } from "@/components/docs-page/visual-editor-section/VisualEditorSection";
11+
import { FernCliVersionDisplayAsync } from "@/components/docs-page/FernCliVersionDisplayAsync";
12+
import { GithubSourceAsync } from "@/components/docs-page/GithubSourceAsync";
13+
import { VisualEditorSectionAsync } from "@/components/docs-page/visual-editor-section/VisualEditorSectionAsync";
14+
import { Skeleton } from "@/components/ui/skeleton";
1515
import { getDocsSiteUrl } from "@/utils/getDocsSiteUrl";
1616
import { parseDocsUrlParam } from "@/utils/parseDocsUrlParam";
1717
import type { EncodedDocsUrl } from "@/utils/types";
@@ -43,79 +43,11 @@ export default async function Page(props: {
4343
notFound();
4444
}
4545

46-
let githubUrl = undefined;
47-
let githubAuthState: GithubAuthState = {
48-
validationResult: {
49-
ok: false,
50-
error: {
51-
type: "UNEXPECTED_ERROR",
52-
message: ""
53-
}
54-
},
55-
sourceRepo: undefined,
56-
isLoading: false
57-
};
58-
59-
try {
60-
const urlResult = await getDocsGithubUrl({
61-
url: encodedDocsUrl,
62-
token: session.accessToken
63-
});
64-
65-
if (!urlResult.success) {
66-
if (urlResult.error.type === "DOMAIN_NOT_REGISTERED") {
67-
githubAuthState.validationResult = {
68-
ok: false,
69-
error: {
70-
type: "UNEXPECTED_ERROR",
71-
message: "Domain not registered."
72-
}
73-
};
74-
} else {
75-
githubAuthState.validationResult = {
76-
ok: false,
77-
error: urlResult.error
78-
};
79-
}
80-
} else {
81-
githubUrl = urlResult.githubUrl;
82-
83-
try {
84-
// Parallelize validation and metadata fetching for better performance
85-
const [validation, sourceRepo] = await Promise.all([
86-
validateGithubRepoAccess(
87-
orgName,
88-
docsUrl,
89-
{
90-
type: "url",
91-
githubUrl
92-
},
93-
true // Skip cache for now, since this cache was causing issues with validating repos
94-
),
95-
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
96-
getGithubSourceMetadataHandler({
97-
githubUrl,
98-
userId: session.user.sub
99-
}).catch((error) => {
100-
console.error("Failed to fetch source repo metadata:", error);
101-
return undefined;
102-
})
103-
]);
104-
105-
githubAuthState = {
106-
validationResult: validation,
107-
// Only include sourceRepo if validation succeeded
108-
sourceRepo: validation.ok ? sourceRepo : undefined,
109-
isLoading: false
110-
};
111-
} catch (error) {
112-
console.error("Failed to validate GitHub access:", error);
113-
// Keep default false state
114-
}
115-
}
116-
} catch (error) {
117-
console.error(error);
118-
}
46+
// Start expensive operations in parallel without awaiting
47+
// These promises will be passed to async components that will await them independently
48+
// This allows React to render the page shell and fallbacks immediately
49+
const githubUrlPromise = getDocsGithubUrl(docsUrl, session.accessToken);
50+
const githubAuthStatePromise = getGitHubAuthState(docsUrl, session.accessToken, orgName, docsUrl, session);
11951

12052
return (
12153
<div className="flex w-full flex-col gap-4">
@@ -125,24 +57,30 @@ export default async function Page(props: {
12557
<div className="flex flex-wrap gap-x-10 gap-y-4">
12658
<div className="flex w-fit flex-col gap-2">
12759
<p>Source</p>
128-
<GithubSource docsUrl={docsUrl} githubUrl={githubUrl} />
60+
<Suspense fallback={<Skeleton className="h-4 w-24" />}>
61+
<GithubSourceAsync docsUrl={docsUrl} githubUrlPromise={githubUrlPromise} />
62+
</Suspense>
12963
</div>
130-
<FernCliVersionDisplay
131-
orgName={orgName}
132-
docsUrl={docsUrl}
133-
githubUrl={githubUrl}
134-
baseBranch={githubAuthState.sourceRepo?.baseBranch}
135-
/>
64+
<Suspense fallback={<Skeleton className="h-4 w-24" />}>
65+
<FernCliVersionDisplayAsync
66+
orgName={orgName}
67+
docsUrl={docsUrl}
68+
githubUrlPromise={githubUrlPromise}
69+
githubAuthStatePromise={githubAuthStatePromise}
70+
/>
71+
</Suspense>
13672
</div>
13773
}
13874
/>
139-
<VisualEditorSection
140-
docsUrl={docsUrl}
141-
session={session}
142-
orgName={orgName}
143-
githubAuthState={githubAuthState}
144-
githubUrl={githubUrl}
145-
/>
75+
<Suspense fallback={null}>
76+
<VisualEditorSectionAsync
77+
docsUrl={docsUrl}
78+
session={session}
79+
orgName={orgName}
80+
githubUrlPromise={githubUrlPromise}
81+
githubAuthStatePromise={githubAuthStatePromise}
82+
/>
83+
</Suspense>
14684
</div>
14785
);
14886
}

packages/fern-dashboard/src/app/[orgName]/(visual-editor)/editor/[docsUrl]/[branch]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type React from "react";
55

66
import { ClientMDXProvider } from "@/app/[orgName]/context/ClientMDXProvider";
77
import { OrgNameProvider } from "@/app/[orgName]/context/OrgNameContext";
8-
import getGithubSourceMetadata from "@/app/api/get-github-source-metadata/handler";
8+
import { getGithubSourceMetadata } from "@/app/actions/getGithubSourceMetadata";
99
import type { Auth0OrgName } from "@/app/services/auth0/types";
1010
import { assertAuthAndFetchGithubUrl } from "@/app/services/dal/github/assertAuthAndFetchGithubUrl";
1111
import { getAuthenticatedSessionOrRedirect } from "@/app/services/dal/organization";

packages/fern-dashboard/src/app/actions/getDocsGithubMetadata.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
import { fernToken_admin } from "@fern-api/docs-server";
44

55
import type { GithubAuthState } from "@/components/docs-page/GithubSource";
6-
7-
import getGithubSourceMetadataHandler from "../api/get-github-source-metadata/handler";
86
import { getDocsUrlMetadata } from "../api/utils/getDocsUrlMetadata";
97
import { type Auth0SessionData, getCurrentSessionOrThrow } from "../services/auth0/getCurrentSession";
108
import type { Auth0OrgName } from "../services/auth0/types";
11-
import getDocsGithubUrl from "../services/dal/github/getDocsGithubUrl";
9+
import { getDocsGithubUrl } from "../services/dal/github/getDocsGithubUrl";
1210
import { validateGithubRepoAccess } from "../services/dal/github/validators";
11+
import { getGithubSourceMetadata } from "./getGithubSourceMetadata";
1312

1413
async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, orgName: Auth0OrgName, docsUrl: string) {
1514
let githubAuthState: GithubAuthState = {
@@ -25,10 +24,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
2524
};
2625
let githubUrl: string | undefined;
2726
try {
28-
const urlResult = await getDocsGithubUrl({
29-
url: encodedDocsUrl,
30-
token: session.accessToken
31-
});
27+
const urlResult = await getDocsGithubUrl(encodedDocsUrl, session.accessToken);
3228

3329
if (!urlResult.success) {
3430
if (urlResult.error.type === "DOMAIN_NOT_REGISTERED") {
@@ -63,7 +59,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
6359
true // Skip cache for now, since this cache was causing issues with validating repos
6460
),
6561
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
66-
getGithubSourceMetadataHandler({
62+
getGithubSourceMetadata({
6763
githubUrl,
6864
userId: session.user.sub
6965
}).catch((error: unknown) => {
@@ -102,10 +98,7 @@ export async function getDocsGithubMetadata(docsUrl: string): Promise<{
10298
url: decodedUrl,
10399
token: fernToken_admin() ?? session.accessToken
104100
});
105-
const githubMetadata = await getDocsGithubUrl({
106-
url: decodedUrl,
107-
token: fernToken_admin() ?? session.accessToken
108-
});
101+
const githubMetadata = await getDocsGithubUrl(decodedUrl, fernToken_admin() ?? session.accessToken);
109102
if (!githubMetadata.success) {
110103
return { success: false, error: "Failed to fetch github metadata" };
111104
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import "server-only";
2+
import { cache } from "react";
3+
import type { GithubAuthState } from "@/components/docs-page/GithubSource";
4+
import { getGithubSourceMetadata } from "./getGithubSourceMetadata";
5+
import type { Auth0SessionData } from "../services/auth0/getCurrentSession";
6+
import type { Auth0OrgName } from "../services/auth0/types";
7+
import { type GetDocsGithubUrlResult, getDocsGithubUrl } from "../services/dal/github/getDocsGithubUrl";
8+
import { validateGithubRepoAccess } from "../services/dal/github/validators";
9+
10+
type GetDocsGithubUrlError = Extract<GetDocsGithubUrlResult, { success: false }>["error"];
11+
12+
export type GetGitHubAuthStateResult =
13+
| GithubAuthState
14+
| { success: false; error: GetDocsGithubUrlError };
15+
16+
export const getGitHubAuthState = cache(
17+
async (url: string, token: string, orgName: Auth0OrgName, docsUrl: string, session: Auth0SessionData): Promise<GetGitHubAuthStateResult> => {
18+
const urlResult = await getDocsGithubUrl(url, token);
19+
if (!urlResult.success) {
20+
return { success: false, error: urlResult.error };
21+
}
22+
23+
const githubUrl = urlResult.githubUrl;
24+
let githubAuthState: GithubAuthState = {
25+
validationResult: {
26+
ok: false,
27+
error: {
28+
type: "UNEXPECTED_ERROR",
29+
message: ""
30+
}
31+
},
32+
sourceRepo: undefined,
33+
isLoading: false
34+
};
35+
36+
try {
37+
// Parallelize validation and metadata fetching for better performance
38+
const [validation, sourceRepo] = await Promise.all([
39+
validateGithubRepoAccess(
40+
orgName,
41+
docsUrl,
42+
{
43+
type: "url",
44+
githubUrl
45+
},
46+
true // Skip cache for now, since this cache was causing issues with validating repos
47+
),
48+
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
49+
getGithubSourceMetadata({
50+
githubUrl,
51+
userId: session.user.sub
52+
}).catch((error) => {
53+
console.error("Failed to fetch source repo metadata:", error);
54+
return undefined;
55+
})
56+
]);
57+
58+
githubAuthState = {
59+
validationResult: validation,
60+
// Only include sourceRepo if validation succeeded
61+
sourceRepo: validation.ok ? sourceRepo : undefined,
62+
isLoading: false
63+
};
64+
} catch (error) {
65+
console.error("Failed to validate GitHub access:", error);
66+
// Keep default false state
67+
}
68+
69+
return githubAuthState;
70+
}
71+
);

packages/fern-dashboard/src/app/api/get-github-source-metadata/handler.ts renamed to packages/fern-dashboard/src/app/actions/getGithubSourceMetadata.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use server";
2+
13
import { unstable_cache } from "next/cache";
24

35
import { getFernBotInstallationId, getFernBotOctokitForRepo } from "@/app/services/auth0/fernBotOctokit";
@@ -13,7 +15,7 @@ const EMPTY_RESPONSE: GithubSourceRepo = {
1315
fernBotHasInstallationId: undefined
1416
};
1517

16-
export default async function getGithubSourceMetadataHandler({
18+
export async function getGithubSourceMetadata({
1719
githubUrl,
1820
userId,
1921
skipCache = false
@@ -22,7 +24,7 @@ export default async function getGithubSourceMetadataHandler({
2224
userId: string;
2325
skipCache?: boolean;
2426
}): Promise<GithubSourceRepo> {
25-
async function getGithubSourceMetadata() {
27+
async function fetchGithubSourceMetadata() {
2628
if (githubUrl == null) {
2729
throw new Error("NoGithubUrl");
2830
}
@@ -67,14 +69,14 @@ export default async function getGithubSourceMetadataHandler({
6769
try {
6870
// Only cache successful responses; do not cache failures
6971
const result = skipCache
70-
? getGithubSourceMetadata()
71-
: unstable_cache(getGithubSourceMetadata, [`github-source-${githubUrl}-${userId}`], {
72+
? fetchGithubSourceMetadata()
73+
: unstable_cache(fetchGithubSourceMetadata, [`github-source-${githubUrl}-${userId}`], {
7274
revalidate: 300, // 5 minutes
7375
tags: [`github-source-${githubUrl}`]
7476
})();
7577
return await result;
7678
} catch (error) {
77-
console.error("[getDocsGithubSourceHandler]", error);
79+
console.error("[getGithubSourceMetadata]", error);
7880
// On any error, return EMPTY_RESPONSE (but don't cache the error)
7981
return EMPTY_RESPONSE;
8082
}

packages/fern-dashboard/src/app/api/get-github-source-metadata/route.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

packages/fern-dashboard/src/app/services/dal/github/assertAuthAndFetchGithubUrl.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { DocsUrl } from "@/utils/types";
66
import { getCurrentSession } from "../../auth0/getCurrentSession";
77
import type { Auth0OrgName } from "../../auth0/types";
88
import { assertUserHasOrganizationAccess } from "../organization";
9-
import getDocsGithubUrl from "./getDocsGithubUrl";
9+
import { getDocsGithubUrl } from "./getDocsGithubUrl";
1010
import { assertGithubAccessByUrl } from "./validators";
1111

1212
export const assertAuthAndFetchGithubUrl = cache(
@@ -21,10 +21,7 @@ export const assertAuthAndFetchGithubUrl = cache(
2121
await assertUserHasOrganizationAccess(session.accessToken, orgName);
2222

2323
// Validate GitHub access
24-
const urlResult = await getDocsGithubUrl({
25-
url: docsUrl,
26-
token: session.accessToken
27-
});
24+
const urlResult = await getDocsGithubUrl(docsUrl, session.accessToken);
2825
if (!urlResult.success) {
2926
redirect(`/${orgName}/docs`);
3027
}

0 commit comments

Comments
 (0)