Browse Source

refactor: simplify routing by removing server-side data fetching and hydration across all app pages

master
mortezaei 2 weeks ago
parent
commit
3480a92136
  1. 31
      src/app/[lang]/page.tsx
  2. 4
      src/app/candidate-contact/candidate-contact-client.tsx
  3. 38
      src/app/candidate-contact/page.tsx
  4. 38
      src/app/finding-match/page.tsx
  5. 41
      src/app/intro/page.tsx
  6. 38
      src/app/new-match/page.tsx
  7. 13
      src/app/page.tsx
  8. 48
      src/app/questions-list/page.tsx
  9. 5
      src/app/questions-list/questions-list-client.tsx
  10. 38
      src/app/request-accepted/page.tsx
  11. 38
      src/app/request-sent/page.tsx
  12. 5
      src/app/terms/page.tsx

31
src/app/[lang]/page.tsx

@ -7,11 +7,6 @@ import {
isAuthenticatedToken, isAuthenticatedToken,
MARRIAGE_ENTRY_PATH_COOKIE, MARRIAGE_ENTRY_PATH_COOKIE,
} from "@/lib/entry-route-cache"; } from "@/lib/entry-route-cache";
import {
getSubmitPath,
hasCompletedMarriageProfileBasics,
} from "@/lib/get-submit-path";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { localizePath } from "@/translations/config"; import { localizePath } from "@/translations/config";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@ -27,7 +22,7 @@ export default async function LocaleEntryPage({
cookieStore.get("habib_token")?.value; cookieStore.get("habib_token")?.value;
const hasToken = isAuthenticatedToken(token); const hasToken = isAuthenticatedToken(token);
// 1. If not authenticated, render <Intro /> instantly in SSR
// 1. If not authenticated, render <Intro /> instantly
if (!hasToken) { if (!hasToken) {
return <Intro />; return <Intro />;
} }
@ -45,26 +40,6 @@ export default async function LocaleEntryPage({
redirect(localizePath(cachedEntryPath, lang)); redirect(localizePath(cachedEntryPath, lang));
} }
// 3. Deep SSR Profile Resolution: resolve profile & redirect directly on server
const profile = await fetchProfileSSR(token);
if (profile) {
const isBasicsCompleted = hasCompletedMarriageProfileBasics(profile);
if (!isBasicsCompleted) {
return <Intro />;
}
const targetPath = getSubmitPath(profile);
if (targetPath === "/intro") {
return <Intro />;
}
redirect(localizePath(targetPath, lang));
}
// 4. Graceful Fallback if server fetch was unreachable
return (
<>
<EntryRouteResolver anonymousEntryVisible={false} />
</>
);
// 3. Instant non-blocking client-side resolver fallback
return <EntryRouteResolver anonymousEntryVisible={false} />;
} }

4
src/app/candidate-contact/candidate-contact-client.tsx

@ -60,8 +60,8 @@ export default function CandidateContactClient() {
} }
}, [profile, router, locale]); }, [profile, router, locale]);
// Signal Flutter to lift its loading cover once the profile is available.
useHabibWebReady(!!profile && !isProfileLoading);
// Signal Flutter to lift its loading cover immediately with 0ms latency.
useHabibWebReady(true);
const isRedirecting = useMemo(() => { const isRedirecting = useMemo(() => {
if (!profile) return false; if (!profile) return false;

38
src/app/candidate-contact/page.tsx

@ -1,39 +1,5 @@
import { cookies } from "next/headers";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { isAuthenticatedToken } from "@/lib/entry-route-cache";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import CandidateContactClient from "./candidate-contact-client"; import CandidateContactClient from "./candidate-contact-client";
export const dynamic = "force-dynamic";
export default async function CandidateContactPage() {
const cookieStore = await cookies();
const token =
cookieStore.get("HABIB_TOKEN")?.value ??
cookieStore.get("habib_token")?.value;
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 10 * 1000 },
},
});
if (isAuthenticatedToken(token)) {
await queryClient.prefetchQuery({
queryKey: marriageQueryKeys.profile(),
queryFn: () => fetchProfileSSR(token!),
});
}
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<CandidateContactClient />
</HydrationBoundary>
);
export default function CandidateContactPage() {
return <CandidateContactClient />;
} }

38
src/app/finding-match/page.tsx

@ -1,39 +1,5 @@
import { cookies } from "next/headers";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { isAuthenticatedToken } from "@/lib/entry-route-cache";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import FindingMatchClient from "./finding-match-client"; import FindingMatchClient from "./finding-match-client";
export const dynamic = "force-dynamic";
export default async function FindingMatchPage() {
const cookieStore = await cookies();
const token =
cookieStore.get("HABIB_TOKEN")?.value ??
cookieStore.get("habib_token")?.value;
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 10 * 1000 },
},
});
if (isAuthenticatedToken(token)) {
await queryClient.prefetchQuery({
queryKey: marriageQueryKeys.profile(),
queryFn: () => fetchProfileSSR(token!),
});
}
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<FindingMatchClient />
</HydrationBoundary>
);
export default function FindingMatchPage() {
return <FindingMatchClient />;
} }

41
src/app/intro/page.tsx

@ -1,42 +1,5 @@
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import { fetchConfigSSR } from "@/lib/ssr-fetch";
import IntroClient from "./intro-client"; import IntroClient from "./intro-client";
export const dynamic = "force-dynamic";
/**
* Server Component wrapper for the Intro page.
*
* Follows the same SSR-prefetch pattern as questions-list/page.tsx:
* create a server QueryClient, prefetch critical data, dehydrate, and
* wrap the client component in HydrationBoundary so TanStack Query
* hydrates the cache instantly no client-side waterfall.
*
* Config is the only data Intro needs. Even if the fetch fails, IntroClient
* has a local fallback image so the UI is never broken.
*/
export default async function IntroPage() {
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 30 * 1000 },
},
});
// Prefetch marriage config — Intro uses it for the video thumbnail.
// Failure is non-blocking because IntroClient has a fallbackSrc.
await queryClient.prefetchQuery({
queryKey: marriageQueryKeys.config(),
queryFn: () => fetchConfigSSR(),
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<IntroClient />
</HydrationBoundary>
);
export default function IntroPage() {
return <IntroClient />;
} }

38
src/app/new-match/page.tsx

@ -1,39 +1,5 @@
import { cookies } from "next/headers";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { isAuthenticatedToken } from "@/lib/entry-route-cache";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import NewMatchClient from "./new-match-client"; import NewMatchClient from "./new-match-client";
export const dynamic = "force-dynamic";
export default async function NewMatchPage() {
const cookieStore = await cookies();
const token =
cookieStore.get("HABIB_TOKEN")?.value ??
cookieStore.get("habib_token")?.value;
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 10 * 1000 },
},
});
if (isAuthenticatedToken(token)) {
await queryClient.prefetchQuery({
queryKey: marriageQueryKeys.profile(),
queryFn: () => fetchProfileSSR(token!),
});
}
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<NewMatchClient />
</HydrationBoundary>
);
export default function NewMatchPage() {
return <NewMatchClient />;
} }

13
src/app/page.tsx

@ -5,11 +5,6 @@ import {
isAuthenticatedToken, isAuthenticatedToken,
MARRIAGE_ENTRY_PATH_COOKIE, MARRIAGE_ENTRY_PATH_COOKIE,
} from "@/lib/entry-route-cache"; } from "@/lib/entry-route-cache";
import {
getSubmitPath,
hasCompletedMarriageProfileBasics,
} from "@/lib/get-submit-path";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { import {
defaultLocale, defaultLocale,
isLocale, isLocale,
@ -57,13 +52,5 @@ export default async function RootPage() {
redirect(localizePath(cachedEntryPath, targetLocale)); redirect(localizePath(cachedEntryPath, targetLocale));
} }
const profile = await fetchProfileSSR(token);
if (profile) {
const targetPath = hasCompletedMarriageProfileBasics(profile)
? getSubmitPath(profile)
: "/intro";
redirect(localizePath(targetPath, targetLocale));
}
redirect(`/${targetLocale}`); redirect(`/${targetLocale}`);
} }

48
src/app/questions-list/page.tsx

@ -1,49 +1,5 @@
import { cookies } from "next/headers";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { isAuthenticatedToken } from "@/lib/entry-route-cache";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import QuestionsListClient from "./questions-list-client"; import QuestionsListClient from "./questions-list-client";
export const dynamic = "force-dynamic";
/**
* Server Component wrapper for questions-list.
*
* Prefetches the marriage profile server-side using the HABIB_TOKEN cookie
* (Flutter sets it via WebViewCookieManager before loadRequest). The profile
* determines which page the user should see having it in the HTML avoids
* the white-flash caused by waiting for the client-side API call.
*
* The sections/form-overview loads client-side with shimmer that's fine.
*/
export default async function QuestionsListPage() {
const cookieStore = await cookies();
const token =
cookieStore.get("HABIB_TOKEN")?.value ??
cookieStore.get("habib_token")?.value;
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 10 * 1000 },
},
});
if (isAuthenticatedToken(token)) {
await queryClient.prefetchQuery({
queryKey: marriageQueryKeys.profile(),
queryFn: () => fetchProfileSSR(token!),
});
}
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<QuestionsListClient />
</HydrationBoundary>
);
export default function QuestionsListPage() {
return <QuestionsListClient />;
} }

5
src/app/questions-list/questions-list-client.tsx

@ -136,9 +136,8 @@ export default function QuestionsListClient() {
void fetchGeoCountryCode(); void fetchGeoCountryCode();
}, []); }, []);
// Signal Flutter to lift its loading cover once the profile is available
// (either from SSR hydration or client-side fetch).
useHabibWebReady(!!profile && !isProfileLoading);
// Signal Flutter to lift its loading cover immediately with 0ms latency.
useHabibWebReady(true);
const startMatchMutation = useStartMarriageMatchMutation({ const startMatchMutation = useStartMarriageMatchMutation({
onSuccess: () => { onSuccess: () => {

38
src/app/request-accepted/page.tsx

@ -1,39 +1,5 @@
import { cookies } from "next/headers";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { isAuthenticatedToken } from "@/lib/entry-route-cache";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import RequestAcceptedClient from "./request-accepted-client"; import RequestAcceptedClient from "./request-accepted-client";
export const dynamic = "force-dynamic";
export default async function RequestAcceptedPage() {
const cookieStore = await cookies();
const token =
cookieStore.get("HABIB_TOKEN")?.value ??
cookieStore.get("habib_token")?.value;
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 10 * 1000 },
},
});
if (isAuthenticatedToken(token)) {
await queryClient.prefetchQuery({
queryKey: marriageQueryKeys.profile(),
queryFn: () => fetchProfileSSR(token!),
});
}
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<RequestAcceptedClient />
</HydrationBoundary>
);
export default function RequestAcceptedPage() {
return <RequestAcceptedClient />;
} }

38
src/app/request-sent/page.tsx

@ -1,39 +1,5 @@
import { cookies } from "next/headers";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { isAuthenticatedToken } from "@/lib/entry-route-cache";
import { fetchProfileSSR } from "@/lib/ssr-fetch";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import RequestSentClient from "./request-sent-client"; import RequestSentClient from "./request-sent-client";
export const dynamic = "force-dynamic";
export default async function RequestSentPage() {
const cookieStore = await cookies();
const token =
cookieStore.get("HABIB_TOKEN")?.value ??
cookieStore.get("habib_token")?.value;
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 10 * 1000 },
},
});
if (isAuthenticatedToken(token)) {
await queryClient.prefetchQuery({
queryKey: marriageQueryKeys.profile(),
queryFn: () => fetchProfileSSR(token!),
});
}
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<RequestSentClient />
</HydrationBoundary>
);
export default function RequestSentPage() {
return <RequestSentClient />;
} }

5
src/app/terms/page.tsx

@ -18,9 +18,8 @@ export default function TermsRoute() {
const router = useRouter(); const router = useRouter();
const { locale, dictionary: t } = useI18n(); const { locale, dictionary: t } = useI18n();
// Signal Flutter when profile data is available (or failed — the page
// has its own loading/error UI so the cover can safely be removed).
useHabibWebReady(!isLoading);
// Signal Flutter to lift its loading cover immediately with 0ms latency.
useHabibWebReady(true);
useEffect(() => { useEffect(() => {
if (!isLoading && profile) { if (!isLoading && profile) {

Loading…
Cancel
Save