Skip to content

Commit 02dfb39

Browse files
committed
Improves API caching and error handling
Enhances caching mechanisms in API responses by introducing revalidation and refining cache-control headers for better performance. Adds a loading state in the ProjectsContextProvider to prevent redundant fetch calls and ensures error handling logs are more descriptive. Simplifies code style by removing semicolons and adjusts default state initialization to optimize user experience.
1 parent d61281f commit 02dfb39

File tree

4 files changed

+48
-35
lines changed

4 files changed

+48
-35
lines changed
Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
1-
import { NextRequest, NextResponse } from "next/server";
2-
import { session, userGitHubClient } from "@/composition";
3-
import { makeUnauthenticatedAPIErrorResponse } from "@/common";
1+
import { NextRequest, NextResponse } from "next/server"
2+
import { session, userGitHubClient } from "@/composition"
3+
import { makeUnauthenticatedAPIErrorResponse } from "@/common"
4+
import { revalidatePath } from "next/cache"
5+
46

57
interface GetBlobParams {
6-
owner: string;
7-
repository: string;
8-
path: [string];
8+
owner: string
9+
repository: string
10+
path: [string]
911
}
1012

11-
export async function GET(
12-
req: NextRequest,
13-
{ params }: { params: Promise<GetBlobParams> }
14-
) {
15-
const isAuthenticated = await session.getIsAuthenticated();
13+
export async function GET(req: NextRequest, { params }: { params: Promise<GetBlobParams> } ) {
14+
const isAuthenticated = await session.getIsAuthenticated()
1615
if (!isAuthenticated) {
17-
return makeUnauthenticatedAPIErrorResponse();
16+
return makeUnauthenticatedAPIErrorResponse()
1817
}
19-
const { path: paramsPath, owner, repository } = await params;
20-
const path = paramsPath.join("/");
18+
const { path: paramsPath, owner, repository } = await params
19+
const path = paramsPath.join("/")
2120
const item = await userGitHubClient.getRepositoryContent({
2221
repositoryOwner: owner,
2322
repositoryName: repository,
2423
path: path,
25-
ref: req.nextUrl.searchParams.get("ref") ?? undefined,
26-
});
27-
const url = new URL(item.downloadURL);
24+
ref: req.nextUrl.searchParams.get("ref") ?? undefined
25+
})
26+
const url = new URL(item.downloadURL)
2827
const imageRegex = /\.(jpg|jpeg|png|webp|avif|gif)$/;
29-
const res = await fetch(url);
30-
const file = await res.blob();
31-
32-
const headers = new Headers();
33-
if (new RegExp(imageRegex).exec(path)) {
34-
const cacheExpirationInSeconds = 60 * 60 * 24 * 30; // 30 days
35-
headers.set("Content-Type", "image/*");
36-
headers.set("Cache-Control", `max-age=${cacheExpirationInSeconds}`);
37-
} else {
28+
const res = await fetch(url, { next: { revalidate: 6000 } })
29+
const file = await res.blob()
30+
revalidatePath('/(authed)/projects')
31+
const headers = new Headers()
32+
if (res.status !== 200) {
3833
headers.set("Content-Type", "text/plain");
34+
headers.set("Cache-Control", `max-age=3000`)
3935
}
40-
return new NextResponse(file, { status: 200, headers });
36+
if (new RegExp(imageRegex).exec(path)) {
37+
const cacheExpirationInSeconds = 60 * 60 * 24 * 30 // 30 days
38+
headers.set("Content-Type", "image/*");
39+
headers.set("Cache-Control", `max-age=${cacheExpirationInSeconds}`)
40+
}
41+
return new NextResponse(file, { status: 200, headers })
4142
}

src/app/api/projects/route.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import { NextResponse } from "next/server"
2-
import { projectDataSource } from "@/composition"
1+
import { NextResponse } from "next/server";
2+
import { projectDataSource } from "@/composition";
3+
34

45
export async function GET() {
6+
const projects = await projectDataSource.getProjects()
7+
return NextResponse.json({ projects})
8+
}
9+
10+
export async function POST() {
511
const projects = await projectDataSource.refreshProjects()
612
return NextResponse.json({ projects })
7-
}
13+
}

src/features/projects/domain/CachingProjectDataSource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Project from "./Project";
22
import IProjectDataSource from "./IProjectDataSource";
33
import IProjectRepository from "./IProjectRepository";
4-
import { revalidatePath } from "next/cache";
4+
55

66
export default class CachingProjectDataSource implements IProjectDataSource {
77
private dataSource: IProjectDataSource;

src/features/projects/view/ProjectsContextProvider.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState, useEffect } from "react";
3+
import { useState, useEffect, useRef } from "react";
44
import { ProjectsContext } from "@/common";
55
import { Project } from "@/features/projects/domain";
66

@@ -11,8 +11,9 @@ const ProjectsContextProvider = ({
1111
initialProjects?: Project[];
1212
children?: React.ReactNode;
1313
}) => {
14-
const [refreshed, setRefreshed] = useState<boolean>(false);
14+
const [refreshed, setRefreshed] = useState<boolean>(true);
1515
const [projects, setProjects] = useState<Project[]>(initialProjects || []);
16+
const isLoadingRef = useRef(false);
1617

1718
const hasProjectChanged = (value: Project[]) =>
1819
value.some((project, index) => {
@@ -32,6 +33,9 @@ const ProjectsContextProvider = ({
3233
// Trigger background refresh after initial mount
3334
useEffect(() => {
3435
const refreshProjects = () => {
36+
if (isLoadingRef.current) return;
37+
isLoadingRef.current = true;
38+
3539
fetch("/api/projects")
3640
.then((res) => res.json())
3741
.then(
@@ -40,9 +44,11 @@ const ProjectsContextProvider = ({
4044
hasProjectChanged(projects) &&
4145
setProjectsAndRefreshed(projects)
4246
)
43-
.catch((error) => console.log("Failed to refresh projects", error));
47+
.catch((error) => console.error("Failed to refresh projects", error))
48+
.finally(() => {
49+
isLoadingRef.current = false;
50+
});
4451
};
45-
4652
// Initial refresh
4753
refreshProjects();
4854

0 commit comments

Comments
 (0)