{
+ const context = createServerSidePropsContext(target);
+ const route = matchRoute(routes, context.path);
+
+ if (!route?.getServerSideProps) {
+ return {};
}
- return props;
+ return normalizeLoaderResult(await route.getServerSideProps(context));
}
-export async function render(url: string) {
- const staticProps = await loadStaticProps(url);
- const serverProps = await loadServerSideProps(url);
+export async function render(target: string) {
+ const staticProps = await loadStaticProps(target);
+ const serverProps = await loadServerSideProps(target);
const props = { ...staticProps, ...serverProps };
- return renderWithProps(url, props);
+ return renderWithProps(target, props);
}
-export function renderWithProps(url: string, props: RouteProps) {
- const html = renderToString();
+export function renderWithProps(target: string, props: RouteProps) {
+ const context = createServerSidePropsContext(target);
+ const html = renderToString();
return { html, props };
}
diff --git a/src/pages/About.tsx b/src/pages/About.tsx
index b91cfc9..1cebb17 100644
--- a/src/pages/About.tsx
+++ b/src/pages/About.tsx
@@ -22,6 +22,8 @@ export default function About(props: AboutProps) {
);
diff --git a/src/pages/UserProfile.tsx b/src/pages/UserProfile.tsx
index 5007198..831b9c7 100644
--- a/src/pages/UserProfile.tsx
+++ b/src/pages/UserProfile.tsx
@@ -36,7 +36,6 @@ export async function getServerSideProps() {
}
export default function UserProfile({ user, generatedAt, builtAt }: UserProfileProps) {
- console.log('UserProfile', {user, generatedAt, builtAt});
return (
User Profile (Sample)
diff --git a/src/router.tsx b/src/router.tsx
index 9b5cff8..af6b9a5 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -3,12 +3,22 @@ import * as React from 'react';
// --- Types ---
export type RouteProps = Record;
+export type RoutePropsResult = RouteProps | { props: RouteProps };
+export type QueryValue = string | string[];
+export type QueryParams = Record;
+
+export interface ServerSidePropsContext {
+ url: string;
+ path: string;
+ query: QueryParams;
+}
export interface Route {
path: string;
component: React.ComponentType;
- getStaticProps?: () => RouteProps | Promise;
- getServerSideProps?: () => RouteProps | Promise;
+ getStaticProps?: () => RoutePropsResult | Promise;
+ getServerSideProps?: (context: ServerSidePropsContext) => RoutePropsResult | Promise;
+ hasServerSideProps?: boolean;
}
export interface RouterContextValue {
@@ -40,18 +50,32 @@ function getSsrRoutes(): string[] {
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
}
-async function fetchRouteProps(path: string): Promise {
- const shouldUseRuntimeProps = import.meta.env.DEV || getSsrRoutes().includes(path);
+function parseTarget(target: string): { path: string; url: string } {
+ const parsed = new URL(target, window.location.origin);
+ const path = normalizePath(parsed.pathname);
+
+ return {
+ path,
+ url: `${path}${parsed.search}`,
+ };
+}
+
+async function fetchRouteProps(route: Route | undefined, target: string): Promise {
+ const { path, url } = parseTarget(target);
+ const shouldUseRuntimeProps =
+ import.meta.env.DEV ||
+ getSsrRoutes().includes(path) ||
+ Boolean(route?.hasServerSideProps || route?.getServerSideProps);
if (shouldUseRuntimeProps) {
try {
- const devPropsUrl = `/__matcha_props?path=${encodeURIComponent(path)}`;
- const res = await fetch(devPropsUrl, { cache: 'no-store' });
+ const runtimePropsUrl = `/__matcha_props?path=${encodeURIComponent(url)}`;
+ const res = await fetch(runtimePropsUrl, { cache: 'no-store' });
if (res.ok) {
return await res.json() as RouteProps;
}
} catch {
- // Dev props endpoint failed, fall through to static props.
+ // Runtime props endpoint failed, fall through to static props.
}
}
@@ -73,36 +97,38 @@ export function Router({ routes, initialPath, initialProps }: RouterProps) {
const [isLoading, setIsLoading] = React.useState(false);
const navigate = React.useCallback(async (to: string) => {
- const normalized = normalizePath(to);
+ const { path: nextPath, url } = parseTarget(to);
+ const route = matchRoute(routes, nextPath);
setIsLoading(true);
- const newProps = await fetchRouteProps(normalized);
+ const newProps = await fetchRouteProps(route, url);
window.history.pushState({ props: newProps }, '', to);
- setPath(normalized);
+ setPath(nextPath);
setProps(newProps);
setIsLoading(false);
- }, []);
+ }, [routes]);
// Handle browser back/forward
React.useEffect(() => {
const onPopState = async (e: PopStateEvent) => {
- const newPath = normalizePath(window.location.pathname);
- setPath(newPath);
+ const { path: nextPath, url } = parseTarget(window.location.href);
+ const route = matchRoute(routes, nextPath);
+ setPath(nextPath);
// Use cached props from history state, or fetch
if (e.state?.props) {
setProps(e.state.props as RouteProps);
} else {
setIsLoading(true);
- const newProps = await fetchRouteProps(newPath);
+ const newProps = await fetchRouteProps(route, url);
setProps(newProps);
setIsLoading(false);
}
};
window.addEventListener('popstate', onPopState);
return () => window.removeEventListener('popstate', onPopState);
- }, []);
+ }, [routes]);
const route = matchRoute(routes, path);
if (!route) {
diff --git a/src/routes.ts b/src/routes.ts
index b877e7d..d41781b 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -1,10 +1,15 @@
import type { Route } from './router.js';
import Home from './pages/Home.js';
-import About, { getStaticProps as AboutGetStaticProps } from './pages/About.js';
-import UserProfile, { getStaticProps as UserProfileGetStaticProps, getServerSideProps as UserProfileGetServerSideProps } from './pages/UserProfile.js';
+import * as AboutPage from './pages/About.js';
+import * as UserProfilePage from './pages/UserProfile.js';
export const routes: Route[] = [
{ path: '/', component: Home },
- { path: '/about', component: About, getStaticProps: AboutGetStaticProps },
- { path: '/user-profile', component: UserProfile, getStaticProps: UserProfileGetStaticProps, getServerSideProps: UserProfileGetServerSideProps },
+ { path: '/about', component: AboutPage.default, getStaticProps: AboutPage.getStaticProps },
+ {
+ path: '/user-profile',
+ component: UserProfilePage.default,
+ getStaticProps: UserProfilePage.getStaticProps,
+ getServerSideProps: UserProfilePage.getServerSideProps,
+ },
];