@@ -47,6 +47,7 @@ export interface SocialPreviewOptions {
4747
4848export interface SeoOptions {
4949 aiAttribution ?: string ;
50+ robotsRules ?: string ;
5051}
5152
5253export interface AnalyticsOptions {
@@ -239,8 +240,10 @@ ${slugs
239240 /*
240241 * Step 3.4: SEO configuration (optional)
241242 * AI attribution for proper citation in AI-generated content
243+ * robots.txt rules (the sitemap line is added automatically)
242244 */
243245 const AI_ATTRIBUTION = ${ str ( seo ?. aiAttribution ) } ;
246+ const ROBOTS_RULES = ${ tpl ( seo ?. robotsRules ?. trim ( ) ) } ;
244247
245248 /*
246249 * Step 3.5: analytics configuration (optional)
679682 // Use the original Notion site domain instead of www.notion.so
680683 url.hostname = NOTION_SITE_DOMAIN;
681684 if (url.pathname === '/robots.txt') {
682- return new Response('Sitemap: https://' + MY_DOMAIN + '/sitemap.xml');
685+ return new Response((ROBOTS_RULES || 'User-agent: *\\nAllow: /') + '\\n\\nSitemap: https://' + MY_DOMAIN + '/sitemap.xml\\n ');
683686 }
684687 if (url.pathname === '/sitemap.xml') {
685688 let response = new Response(generateSitemap());
696699 const slug = url.pathname.replace('/og-image/', '');
697700 return generateOgImage(slug);
698701 }
702+ if (isUnknownPageNavigation(request, url.pathname)) {
703+ return notFoundResponse(url);
704+ }
699705 let response;
700706 const isAppJs = url.pathname.startsWith('/app') && url.pathname.endsWith('js');
701707 const isAssetJs = url.pathname.startsWith('/_assets/') && url.pathname.endsWith('.js');
725731 pageId = SLUG_TO_PAGE[slug];
726732 } else {
727733 const match = refPath.match(/[0-9a-f]{32}/);
728- if (match) pageId = match[0];
734+ // Any other document path was served as the custom 404 page
735+ pageId = match ? match[0] : CUSTOM_404_PAGE_ID;
729736 }
730737 } catch (e) {}
731738 }
803810
804811 // Handle 404 with custom page if configured (Issue #12)
805812 if (response.status === 404 && CUSTOM_404_PAGE_ID !== '') {
806- const notFoundUrl = new URL(url);
807- notFoundUrl.pathname = '/' + CUSTOM_404_PAGE_ID;
808- const notFoundResponse = await fetch(notFoundUrl.toString(), {
809- headers: PAGE_FETCH_HEADERS,
810- });
811- // Return custom 404 page content with 404 status
812- response = new Response(notFoundResponse.body, {
813- status: 404,
814- statusText: 'Not Found',
815- headers: notFoundResponse.headers,
816- });
817- response.headers.delete('Content-Security-Policy');
818- response.headers.delete('X-Content-Security-Policy');
819- return appendJavascript(response, SLUG_TO_PAGE, '404', url);
813+ return notFoundResponse(url);
820814 }
821815
822816 // Get current slug from page ID for canonical URL
10661060 }
10671061
10681062 class BodyRewriter {
1069- constructor(SLUG_TO_PAGE) {
1063+ constructor(SLUG_TO_PAGE, notFound ) {
10701064 this.SLUG_TO_PAGE = SLUG_TO_PAGE;
1065+ this.notFound = notFound;
10711066 }
10721067 element(element) {
10731068 // Add custom header HTML at the top of body if configured (Issue #20)
10781073 <script>
10791074 if (window.CONFIG) window.CONFIG.domainBaseUrl = 'https://\${MY_DOMAIN}';
10801075 const SLUG_TO_PAGE = \${JSON.stringify(this.SLUG_TO_PAGE)};
1076+ // Keep the requested URL when the custom 404 page is shown
1077+ const NOT_FOUND_PATH = \${this.notFound} ? location.pathname : null;
10811078 const PAGE_TO_SLUG = {};
10821079 const slugs = [];
10831080 const pages = [];
11721169 }, 2000);
11731170 const replaceState = window.history.replaceState;
11741171 window.history.replaceState = function(state) {
1175- if (arguments[1] !== 'bypass' && slugs.includes(getSlug())) return;
1172+ if (arguments[1] !== 'bypass' && ( slugs.includes(getSlug()) || location.pathname === NOT_FOUND_PATH )) return;
11761173 return replaceState.apply(window.history, arguments);
11771174 };
11781175 const pushState = window.history.pushState;
@@ -1196,6 +1193,56 @@ ${
11961193 }
11971194 }
11981195
1196+ // Notion answers 200 with its app shell for any path, so decide here whether a
1197+ // page navigation points at a real page: the root, a slug, or a page ID.
1198+ function isPageNavigation(request) {
1199+ if (request.method !== 'GET' && request.method !== 'HEAD') return false;
1200+ const dest = request.headers.get('Sec-Fetch-Dest');
1201+ return dest ? dest === 'document' : (request.headers.get('Accept') || '').includes('text/html');
1202+ }
1203+
1204+ // Paths opened directly in the browser that are not pages (images, files)
1205+ const NON_PAGE_PATH = /^\\/(image|images|f|signed|api|_assets)\\//;
1206+
1207+ function isUnknownPageNavigation(request, pathname) {
1208+ if (!isPageNavigation(request) || NON_PAGE_PATH.test(pathname)) return false;
1209+ if (/[0-9a-f]{32}/.test(pathname) || /\\.[a-z0-9]+$/i.test(pathname)) return false;
1210+ let path = pathname.slice(1);
1211+ try {
1212+ path = decodeURIComponent(path);
1213+ } catch (e) {}
1214+ return !SLUG_TO_PAGE.hasOwnProperty(path) && !SLUG_TO_PAGE.hasOwnProperty(pathname.slice(1));
1215+ }
1216+
1217+ async function notFoundResponse(url) {
1218+ if (CUSTOM_404_PAGE_ID === '') {
1219+ return new Response(
1220+ \`<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Page not found</title></head>
1221+ <body style="font-family:system-ui;text-align:center;padding:60px 20px">
1222+ <h1>404</h1>
1223+ <p>This page could not be found.</p>
1224+ <p><a href="/">Go to the home page</a></p>
1225+ </body></html>\`,
1226+ { status: 404, headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
1227+ );
1228+ }
1229+ const notFoundUrl = new URL(url);
1230+ notFoundUrl.hostname = NOTION_SITE_DOMAIN;
1231+ notFoundUrl.pathname = '/' + CUSTOM_404_PAGE_ID;
1232+ const page = await fetch(notFoundUrl.toString(), {
1233+ headers: PAGE_FETCH_HEADERS,
1234+ });
1235+ // Return custom 404 page content with 404 status
1236+ const response = new Response(page.body, {
1237+ status: 404,
1238+ statusText: 'Not Found',
1239+ headers: page.headers,
1240+ });
1241+ response.headers.delete('Content-Security-Policy');
1242+ response.headers.delete('X-Content-Security-Policy');
1243+ return appendJavascript(response, SLUG_TO_PAGE, '404', url);
1244+ }
1245+
11991246 async function appendJavascript(res, SLUG_TO_PAGE, slug, url) {
12001247 const metaRewriter = new MetaRewriter(slug);
12011248 const headRewriter = new HeadRewriter(slug);
12061253 .on('meta', metaRewriter)
12071254 .on('link', linkRewriter)
12081255 .on('head', headRewriter)
1209- .on('body', new BodyRewriter(SLUG_TO_PAGE))
1256+ .on('body', new BodyRewriter(SLUG_TO_PAGE, res.status === 404 ))
12101257 .transform(res);
12111258 return applyCacheHeaders(transformed, url, contentType);
12121259 }` ;
0 commit comments