Skip to content

Commit a54ebb5

Browse files
author
Nico Schett
committed
feat(use-pages): implement targeted SSR rendering and snapshot optimization
Introduces a high-performance selective rendering mechanism for SSR, allowing the server to render and collect data for specific layouts without the overhead of rendering the entire route tree.
1 parent 8537e3b commit a54ebb5

File tree

3 files changed

+69
-13
lines changed

3 files changed

+69
-13
lines changed

packages/pylon/src/pages/internals.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,31 @@ const useRouteDataRefetch = (): ((names?: string[]) => Promise<void[]>) => {
150150
}
151151

152152
export {RouteDataProvider, useRouteData, useRouteDataRefetch}
153+
154+
// ====================================================================
155+
// 5. SSR PRUNING (Selective Rendering)
156+
// ====================================================================
157+
158+
const SSRPruningContext = createContext<string | null>(null)
159+
160+
/**
161+
* Provider to signal which layout should be the "pruning target" during SSR.
162+
* Components matching this target will skip rendering their children.
163+
*/
164+
export const SSRPruningProvider: React.FC<{
165+
target: string | null
166+
children: React.ReactNode
167+
}> = ({children, target}) => {
168+
return (
169+
<SSRPruningContext.Provider value={target}>
170+
{children}
171+
</SSRPruningContext.Provider>
172+
)
173+
}
174+
175+
/**
176+
* Hook to consume the SSR pruning target.
177+
*/
178+
export const useSSRPruning = () => {
179+
return useContext(SSRPruningContext)
180+
}

packages/pylon/src/plugins/use-pages/build/app-utils.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ function scanDirectory(directory: string, basePath: string = ''): Route | null {
100100
: `${layoutComponentName}`
101101

102102
route.Component = `withLoaderData((props) => <${componentName} children={<Outlet />} {...props} />, "${componentName}")`
103-
route.loader = `loader`
103+
route.loader = `loader("${componentName}")`
104104
route.shouldRevalidate = `(args) => args.defaultShouldRevalidate`
105105

106106
if (route.path === '/') {
@@ -132,7 +132,7 @@ function scanDirectory(directory: string, basePath: string = ''): Route | null {
132132
errorElement: '<ErrorElement standalone={false} />',
133133
lazy: `async () => {const i = await import(${importPath}).catch(() => {window.reload()}); return {Component: withLoaderData(i.default)}}`,
134134
HydrateFallback: 'HydrateFallback',
135-
loader: `loader`
135+
loader: `loader()`
136136
})
137137

138138
pageFound = true
@@ -266,14 +266,23 @@ const HydrateFallback = () => {
266266
function withLoaderData<T>(Component: React.ComponentType<{ data: T }>, name?: string) {
267267
return function WithLoaderDataWrapper(props: T) {
268268
const dataClient = __PYLON_INTERNALS_DO_NOT_USE.useDataClient()
269-
const {useQuery, useHydrateCache} = useMemo(() => dataClient.pageClient(), [])
270-
const {cacheSnapshot, context} = __PYLON_ROUTER_INTERNALS_DO_NOT_USE.useLoaderData() || {};
269+
const pruningTarget = __PYLON_INTERNALS_DO_NOT_USE.useSSRPruning()
271270
271+
const {cacheSnapshot, context} = __PYLON_ROUTER_INTERNALS_DO_NOT_USE.useLoaderData() || {};
272272
const location = __PYLON_ROUTER_INTERNALS_DO_NOT_USE.useLocation()
273273
const [searchParams] = __PYLON_ROUTER_INTERNALS_DO_NOT_USE.useSearchParams()
274274
const searchParamsObject = Object.fromEntries(searchParams.entries())
275275
const params = __PYLON_ROUTER_INTERNALS_DO_NOT_USE.useParams()
276276
277+
// 1. Handle Transparent Ancestors
278+
// If we're optimized-rendering a specific layout, and THIS is not it,
279+
// we just act as a passthrough to skip THIS layout's logic/queries.
280+
if (pruningTarget && name !== pruningTarget) {
281+
return <Outlet />
282+
}
283+
284+
const {useQuery, useHydrateCache} = useMemo(() => dataClient.pageClient(), [])
285+
277286
if(cacheSnapshot) {
278287
useHydrateCache({cacheSnapshot})
279288
}
@@ -290,13 +299,17 @@ function withLoaderData<T>(Component: React.ComponentType<{ data: T }>, name?: s
290299
}
291300
}, [location.pathname, params, searchParamsObject, data, context])
292301
302+
// 2. Handle Pruning Target
303+
// If THIS is the target, we render it but clear its children (the Outlet).
304+
const children = pruningTarget && name === pruningTarget ? null : <Outlet />
305+
293306
return <__PYLON_INTERNALS_DO_NOT_USE.RouteDataProvider props={pageProps} name={name}>
294-
<Component {...(props as any)} {...pageProps} />
307+
<Component {...(props as any)} {...pageProps} children={children} />
295308
</__PYLON_INTERNALS_DO_NOT_USE.RouteDataProvider>
296309
};
297310
}
298311
299-
const loader: __PYLON_ROUTER_INTERNALS_DO_NOT_USE.LoaderFunction = async ({ request }) => {
312+
const loader: (ref?: string) => __PYLON_ROUTER_INTERNALS_DO_NOT_USE.LoaderFunction = (ref) => async ({ request, ...args }) => {
300313
// 1. Skip if request is a JSON-only fetch (e.g., client-side route preloading)
301314
const acceptHeader = request.headers.get('accept')
302315
if (acceptHeader?.includes('application/json')) {
@@ -324,6 +337,9 @@ const loader: __PYLON_ROUTER_INTERNALS_DO_NOT_USE.LoaderFunction = async ({ requ
324337
}
325338
326339
headers.set('Accept', 'application/json') // Ensure the internal request gets JSON
340+
if(ref) {
341+
headers.set('X-Pylon-Route-Ref', ref)
342+
}
327343
328344
const response = await fetchToUse(url.pathname + url.search, {
329345
method: 'GET',

packages/pylon/src/plugins/use-pages/setup/index.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const setup: Plugin['setup'] = async app => {
5858
.default
5959
const client = await import(`${process.cwd()}/.pylon/client/index.js`)
6060

61-
let handler = createStaticHandler(routes)
61+
const handler = createStaticHandler(routes)
6262

6363
app.use(trimTrailingSlash() as any)
6464

@@ -261,23 +261,33 @@ export const setup: Plugin['setup'] = async app => {
261261
})
262262

263263
app.get('*', disableCacheMiddleware as any, async c => {
264-
const context = await handler.query(c.req.raw)
264+
const staticHandlerContext = await handler.query(c.req.raw)
265265

266-
if (context instanceof Response) {
267-
return context
266+
if (staticHandlerContext instanceof Response) {
267+
return staticHandlerContext
268268
}
269269

270-
const router = createStaticRouter(handler.dataRoutes, context)
270+
// X-Pylon-Route-Ref header stores the route ref of the current layout
271+
// This is used to remove the children of the layout to prevent
272+
// rendering overhead
273+
const xPylonRouteRef = c.req.header('x-pylon-route-ref')
274+
275+
const router = createStaticRouter(handler.dataRoutes, staticHandlerContext)
271276

272277
const component = (
273278
<__PYLON_INTERNALS_DO_NOT_USE.DataClientProvider client={client}>
274-
<StaticRouterProvider router={router} context={context} />
279+
<__PYLON_INTERNALS_DO_NOT_USE.SSRPruningProvider
280+
target={xPylonRouteRef || null}>
281+
<StaticRouterProvider
282+
router={router}
283+
context={staticHandlerContext}
284+
/>
285+
</__PYLON_INTERNALS_DO_NOT_USE.SSRPruningProvider>
275286
</__PYLON_INTERNALS_DO_NOT_USE.DataClientProvider>
276287
)
277288

278289
// Check if the request wants JSON, if so, prepare the data
279290
if (c.req.header('accept')?.includes('application/json')) {
280-
const context = c.get('pagesContext' as any) || {}
281291
let cacheSnapshot
282292
try {
283293
client.cache.clear()
@@ -290,6 +300,8 @@ export const setup: Plugin['setup'] = async app => {
290300
}
291301
}
292302

303+
const context = c.get('pagesContext' as any) || {}
304+
293305
return c.json({
294306
cacheSnapshot,
295307
context

0 commit comments

Comments
 (0)