diff --git a/README.md b/README.md
index 3ada3ec..40ec0c7 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ A Next.js-style full-stack meta-framework built on [Bun](https://bun.sh) and Rea
- [Error Pages](#error-pages)
- [Static Files](#static-files)
- [Configuration](#configuration)
- - [Middleware](#middleware)
+ - [Middleware](#middleware)
- [Environment Variables](#environment-variables)
- [How It Works](#how-it-works)
- [Development Server](#development-server)
@@ -402,7 +402,10 @@ The `ctx` parameter has the same shape as the page `server` function context —
Export a `config` object to override the per-route request body limit (default: 10 MB):
```ts
-import type { BunextServerRouteConfig, BunxRouteParams } from "bunext/src/types";
+import type {
+ BunextServerRouteConfig,
+ BunxRouteParams,
+} from "bunext/src/types";
export const config: BunextServerRouteConfig = {
maxRequestBodyMB: 50, // allow up to 50 MB
@@ -482,15 +485,15 @@ const config: BunextConfig = {
export default config;
```
-| Option | Type | Default | Description |
-| -------------- | -------------------------------------------------------------------- | ---------------- | -------------------------------------------------- |
-| `port` | `number` | `7000` | HTTP server port |
-| `origin` | `string` | — | Canonical origin URL |
-| `distDir` | `string` | `.bunext` | Internal artifact directory |
-| `assetsPrefix` | `string` | `_bunext/static` | URL prefix for static assets |
-| `globalVars` | `{ [k: string]: any }` | — | Variables injected globally at build time |
-| `development` | `boolean` | — | Overridden to `true` by `bunext dev` automatically |
-| `middleware` | `(params: BunextConfigMiddlewareParams) => Response \| undefined \| Promise<...>` | — | Global middleware — see [Middleware](#middleware) |
+| Option | Type | Default | Description |
+| -------------- | --------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------- |
+| `port` | `number` | `7000` | HTTP server port |
+| `origin` | `string` | — | Canonical origin URL |
+| `distDir` | `string` | `.bunext` | Internal artifact directory |
+| `assetsPrefix` | `string` | `_bunext/static` | URL prefix for static assets |
+| `globalVars` | `{ [k: string]: any }` | — | Variables injected globally at build time |
+| `development` | `boolean` | — | Overridden to `true` by `bunext dev` automatically |
+| `middleware` | `(params: BunextConfigMiddlewareParams) => Response \| undefined \| Promise<...>` | — | Global middleware — see [Middleware](#middleware) |
### Middleware
@@ -501,7 +504,10 @@ Middleware runs on every request before any routing. Define it in `bunext.config
```ts
// bunext.config.ts
-import type { BunextConfig, BunextConfigMiddlewareParams } from "bunext/src/types";
+import type {
+ BunextConfig,
+ BunextConfigMiddlewareParams,
+} from "bunext/src/types";
const config: BunextConfig = {
middleware: async ({ req, url, server }) => {
@@ -631,6 +637,6 @@ Request
Server-rendered HTML includes:
- `window.__PAGE_PROPS__` — the serialized server function return value, read by `hydrateRoot` on the client.
-- A `\n`;
if (bundledMap?.path) {
- html += ` \n`;
+ html += ` \n`;
}
if (isDevelopment()) {
diff --git a/src/functions/server/web-pages/handle-web-pages.tsx b/src/functions/server/web-pages/handle-web-pages.tsx
index cb0b741..cb48f26 100644
--- a/src/functions/server/web-pages/handle-web-pages.tsx
+++ b/src/functions/server/web-pages/handle-web-pages.tsx
@@ -1,5 +1,7 @@
import type { GrabPageComponentRes } from "../../../types";
import isDevelopment from "../../../utils/is-development";
+import getCache from "../../cache/get-cache";
+import writeCache from "../../cache/write-cache";
import genWebHTML from "./generate-web-html";
import grabPageComponent from "./grab-page-component";
import grabPageErrorComponent from "./grab-page-error-component";
@@ -12,6 +14,24 @@ export default async function handleWebPages({
req,
}: Params): Promise {
try {
+ if (!isDevelopment()) {
+ const url = new URL(req.url);
+ const key = url.pathname + (url.search || "");
+
+ const existing_cache = getCache({ key, paradigm: "html" });
+
+ if (existing_cache) {
+ const res_opts: ResponseInit = {
+ headers: {
+ "Content-Type": "text/html",
+ "X-Bunext-Cache": "HIT",
+ },
+ };
+
+ return new Response(existing_cache, res_opts);
+ }
+ }
+
const componentRes = await grabPageComponent({ req });
return await generateRes(componentRes);
} catch (error: any) {
@@ -65,6 +85,20 @@ async function generateRes({
};
}
+ const cache_page =
+ module.config?.cachePage || serverRes?.cachePage || false;
+ const expiry_seconds = module.config?.cacheExpiry || serverRes?.cacheExpiry;
+
+ if (cache_page && routeParams?.url) {
+ const key = routeParams.url.pathname + (routeParams.url.search || "");
+ writeCache({
+ key,
+ value: html,
+ paradigm: "html",
+ expiry_seconds,
+ });
+ }
+
const res = new Response(html, res_opts);
if (routeParams?.resTransform) {
diff --git a/src/types/index.ts b/src/types/index.ts
index 45ff930..19e3fa9 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -51,6 +51,7 @@ export type BunextConfig = {
middleware?: (
params: BunextConfigMiddlewareParams,
) => Promise | Response | undefined;
+ defaultCacheExpiry?: number;
};
export type BunextConfigMiddlewareParams = {
@@ -157,6 +158,7 @@ export type BunextPageModule = {
server?: BunextPageServerFn;
meta?: BunextPageModuleMeta | BunextPageModuleMetaFn;
Head?: FC;
+ config?: BunextRouteConfig;
};
export type BunextPageModuleMetaFn = (params: {
@@ -197,6 +199,14 @@ export type BunextPageServerFn<
ctx: Omit,
) => Promise>;
+export type BunextRouteConfig = {
+ cachePage?: boolean;
+ /**
+ * Expiry time of the cache in seconds
+ */
+ cacheExpiry?: number;
+};
+
export type BunextPageModuleServerReturn<
T extends { [k: string]: any } = { [k: string]: any },
Q extends { [k: string]: any } = { [k: string]: any },
@@ -205,6 +215,11 @@ export type BunextPageModuleServerReturn<
query?: Q;
redirect?: BunextPageModuleServerRedirect;
responseOptions?: ResponseInit;
+ cachePage?: boolean;
+ /**
+ * Expiry time of the cache in seconds
+ */
+ cacheExpiry?: number;
};
export type BunextPageModuleServerRedirect = {
@@ -250,3 +265,9 @@ export type GlobalHMRControllerObject = {
page_url: string;
target_map?: BundlerCTXMap;
};
+
+export type BunextCacheFileMeta = {
+ date_created: number;
+ paradigm: "html" | "json";
+ expiry_seconds?: number;
+};
diff --git a/src/utils/grab-dir-names.ts b/src/utils/grab-dir-names.ts
index a51da8e..106dc70 100644
--- a/src/utils/grab-dir-names.ts
+++ b/src/utils/grab-dir-names.ts
@@ -6,7 +6,9 @@ export default function grabDirNames() {
const PAGES_DIR = path.join(SRC_DIR, "pages");
const API_DIR = path.join(PAGES_DIR, "api");
const PUBLIC_DIR = path.join(ROOT_DIR, "public");
- const HYDRATION_DST_DIR = path.join(PUBLIC_DIR, "pages");
+ const BUNEXT_PUBLIC_DIR = path.join(PUBLIC_DIR, "__bunext");
+ const HYDRATION_DST_DIR = path.join(BUNEXT_PUBLIC_DIR, "pages");
+ const BUNEXT_CACHE_DIR = path.join(BUNEXT_PUBLIC_DIR, "cache");
const HYDRATION_DST_DIR_MAP_JSON_FILE = path.join(
HYDRATION_DST_DIR,
"map.json",
@@ -54,5 +56,6 @@ export default function grabDirNames() {
BUNX_ROOT_404_PRESET_COMPONENT,
BUNX_ROOT_404_FILE_NAME,
HYDRATION_DST_DIR_MAP_JSON_FILE,
+ BUNEXT_CACHE_DIR,
};
}