From ef7aa528d47752bd53c8ed0680e2b154c2c17737 Mon Sep 17 00:00:00 2001 From: arifal Date: Wed, 20 May 2026 16:08:29 +0700 Subject: [PATCH] add loading and optimize query using cache and pwa --- app/(public)/loading.tsx | 17 ++++++++++++++++ app/(public)/trips/[id]/loading.tsx | 31 +++++++++++++++++++++++++++++ app/(public)/trips/loading.tsx | 29 +++++++++++++++++++++++++++ app/manifest.ts | 31 +++++++++++++++++++++++++++++ app/robots.ts | 9 ++++++++- app/sitemap.ts | 6 ++++++ next.config.ts | 4 ++++ server/services/trip.service.ts | 10 ++++++++-- 8 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 app/(public)/loading.tsx create mode 100644 app/(public)/trips/[id]/loading.tsx create mode 100644 app/(public)/trips/loading.tsx create mode 100644 app/manifest.ts diff --git a/app/(public)/loading.tsx b/app/(public)/loading.tsx new file mode 100644 index 0000000..41183bb --- /dev/null +++ b/app/(public)/loading.tsx @@ -0,0 +1,17 @@ +/** + * Skeleton generik untuk route group `(public)` — fallback streaming bagi + * halaman yang tidak punya `loading.tsx` sendiri (beranda, profil, dll). + */ +export default function Loading() { + return ( +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/app/(public)/trips/[id]/loading.tsx b/app/(public)/trips/[id]/loading.tsx new file mode 100644 index 0000000..94bb4cb --- /dev/null +++ b/app/(public)/trips/[id]/loading.tsx @@ -0,0 +1,31 @@ +/** Skeleton halaman detail trip — tampil instan saat data masih di-fetch. */ +export default function Loading() { + return ( +
+
+ +
+
+ +
+
+
+
+ +
+
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ ))} +
+
+
+
+
+
+
+ ); +} diff --git a/app/(public)/trips/loading.tsx b/app/(public)/trips/loading.tsx new file mode 100644 index 0000000..7cc829c --- /dev/null +++ b/app/(public)/trips/loading.tsx @@ -0,0 +1,29 @@ +/** Skeleton daftar trip — tampil instan saat list masih di-fetch. */ +export default function Loading() { + return ( +
+
+
+ +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+ ); +} diff --git a/app/manifest.ts b/app/manifest.ts new file mode 100644 index 0000000..ec5b1a6 --- /dev/null +++ b/app/manifest.ts @@ -0,0 +1,31 @@ +import type { MetadataRoute } from "next"; +import { siteConfig } from "@/lib/site"; + +/** + * Web app manifest — dideteksi otomatis oleh Next App Router (`` di-inject). Mendukung "Add to Home Screen" di mobile. + */ +export default function manifest(): MetadataRoute.Manifest { + return { + name: `${siteConfig.name} — ${siteConfig.slogan}`, + short_name: siteConfig.name, + description: siteConfig.description, + start_url: "/", + display: "standalone", + background_color: "#f9fafb", + theme_color: "#16a34a", + icons: [ + { + src: "/images/SeTrip.png", + sizes: "512x512", + type: "image/png", + purpose: "any", + }, + { + src: "/SeTrip.ico", + sizes: "any", + type: "image/x-icon", + }, + ], + }; +} diff --git a/app/robots.ts b/app/robots.ts index 2595504..d35f7cf 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -7,7 +7,14 @@ export default function robots(): MetadataRoute.Robots { { userAgent: "*", allow: "/", - disallow: ["/api/", "/profile", "/create-trip"], + disallow: [ + "/api/", + "/admin", + "/profile", + "/create-trip", + "/verify", + "/trips/*/payment", + ], }, ], sitemap: absoluteUrl("/sitemap.xml"), diff --git a/app/sitemap.ts b/app/sitemap.ts index 80c8be6..f56547b 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -24,6 +24,12 @@ export default async function sitemap(): Promise { changeFrequency: "hourly", priority: 0.9, }, + { + url: absoluteUrl("/people"), + lastModified: now, + changeFrequency: "daily", + priority: 0.7, + }, { url: absoluteUrl("/register"), lastModified: now, diff --git a/next.config.ts b/next.config.ts index 289ec70..1b2b1b8 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,10 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { images: { dangerouslyAllowSVG: true, + // AVIF didahulukan — ~30% lebih kecil dari WebP, didukung browser modern. + formats: ["image/avif", "image/webp"], + // Cache hasil optimasi minimal 1 hari supaya tidak re-optimize tiap request. + minimumCacheTTL: 86400, remotePatterns: [ { protocol: "https", diff --git a/server/services/trip.service.ts b/server/services/trip.service.ts index 52f3aad..d8a4973 100644 --- a/server/services/trip.service.ts +++ b/server/services/trip.service.ts @@ -1,3 +1,4 @@ +import { cache } from "react"; import { Prisma } from "@/app/generated/prisma/client"; import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums"; import { tripRepo, type TripFilters } from "@/server/repositories/trip.repo"; @@ -38,13 +39,18 @@ export const tripService = { return tripRepo.findAll(); }, - async getTripById(id: string) { + /** + * Ambil trip by id. Dibungkus `React.cache()` — `generateMetadata`, body + * halaman, dan `opengraph-image` memanggil ini untuk id yang sama dalam satu + * request, jadi query Prisma yang berat ini cukup jalan sekali per request. + */ + getTripById: cache(async (id: string) => { const trip = await tripRepo.findById(id); if (!trip) { throw new Error("Trip tidak ditemukan"); } return trip; - }, + }), async createTrip(input: CreateTripInput) { if (isTripDepartureDayPast(input.date)) {