Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
14 kB ยท 435 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436import { readFileSync } from "node:fs";import { join } from "node:path";
import { withSentryConfig } from "@sentry/nextjs";import type { NextConfig } from "next";
// Read the MCP server version at build time from apps/server/package.json so the// `serverInfo.version` we publish in /.well-known/mcp/server-card.json never drifts.// Falls back to "0.0.0" if the sibling app isn't present in the build context (e.g.// a deploy that excludes apps/server). Exposed to runtime via Next's `env` config.function readMcpServerVersion(): string { try { const pkgPath = join(__dirname, "..", "server", "package.json"); const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version?: string; }; return pkg.version ?? "0.0.0"; } catch { return "0.0.0"; }}
// REMINDER: avoid Clickjacking attacks by setting the frame-ancestors directiveconst securityHeaders = [ { key: "Content-Security-Policy", value: "frame-ancestors 'self' https://shoogle.dev", },];
// Link headers for agent discovery (RFC 8288 / RFC 8631).// service-doc: human-readable docs. service-desc: machine-readable API description.const homepageLinkHeader = [ '</.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json"', '</.well-known/agent-skills/index.json>; rel="agent-skills"; type="application/json"', '</.well-known/mcp.json>; rel="mcp-server"; type="application/json"', '<https://www.openstatus.dev/docs>; rel="service-doc"; type="text/html"', '<https://www.openstatus.dev/openapi.json>; rel="service-desc"; type="application/json"', '<https://www.openstatus.dev/llms.txt>; rel="describedby"; type="text/plain"', '<https://www.openstatus.dev/llms-full.txt>; rel="alternate"; type="text/plain"; title="llms-full"', '<https://www.openstatus.dev/terms>; rel="terms-of-service"', '<https://www.openstatus.dev/privacy>; rel="privacy-policy"',].join(", ");
const agentDiscoveryHeaders = [ { key: "Link", value: homepageLinkHeader, },];
// ICON_SET=nucleo swaps the icon set at resolution time (see packages/icons)const useNucleoIcons = process.env.ICON_SET === "nucleo";
/** @type {import('next').NextConfig} */const nextConfig: NextConfig = { reactStrictMode: true, transpilePackages: ["@openstatus/ui", "@openstatus/api", "next-mdx-remote"], turbopack: useNucleoIcons ? { resolveAlias: { "@openstatus/icons": "@openstatus/icons/nucleo" } } : undefined, webpack: (config) => { if (useNucleoIcons) { config.resolve.alias["@openstatus/icons$"] = "@openstatus/icons/nucleo"; } return config; }, env: { OPENSTATUS_MCP_SERVER_VERSION: readMcpServerVersion(), }, outputFileTracingIncludes: { "/": [ "./node_modules/.pnpm/@google-cloud/tasks/build/esm/src/**/*.json", "./node_modules/@google-cloud/tasks/build/esm/src/**/*.js", ], "/.well-known/agent-skills/index.json": [ "./public/.well-known/agent-skills/**/*.md", ], }, experimental: { turbopackScopeHoisting: false, // serverMinification:false, // barrel-optimize only when unaliased โ the rewrite would bypass the nucleo alias optimizePackageImports: useNucleoIcons ? [] : ["@openstatus/icons"], }, serverExternalPackages: ["@google-cloud/tasks"], expireTime: 180, // 3 minutes logging: { fetches: { fullUrl: true, }, }, images: { remotePatterns: [ { protocol: "https", hostname: "**.public.blob.vercel-storage.com", }, { protocol: "https", hostname: "screenshot.openstat.us", }, { protocol: "https", hostname: "www.openstatus.dev", }, ], }, async headers() { return [ { source: "/(.*)", headers: securityHeaders }, { source: "/", headers: agentDiscoveryHeaders }, ]; }, async redirects() { return [ { source: "/:path*", has: [{ type: "host", value: "docs.openstatus.dev" }], destination: "https://www.openstatus.dev/docs/:path*", permanent: true, }, { source: "/docs/guides/how-to-connect-openstatus-to-claude-code", destination: "/docs/guides/how-to-connect-openstatus-to-your-agent", permanent: true, }, { source: "/legal/terms", destination: "/terms", permanent: true, }, { source: "/legal/privacy", destination: "/privacy", permanent: true, }, { source: "/features/monitoring", destination: "/uptime-monitoring", permanent: true, }, { source: "/features/status-page", destination: "/status-page", permanent: true, }, { source: "/api-monitoring", destination: "/uptime-monitoring", permanent: true, }, { source: "/monitoring-as-code", destination: "/uptime-monitoring", permanent: true, }, { source: "/private-locations", destination: "/uptime-monitoring", permanent: true, }, { source: "/app/:path*", destination: "https://app.openstatus.dev/", permanent: true, }, { source: "/docs/tutorial/how-to-create-monitor", destination: "/docs/tutorial/create-your-first-monitor", permanent: true, }, { source: "/docs/tutorial/how-to-create-status-page", destination: "/docs/tutorial/create-your-first-status-page", permanent: true, }, { source: "/docs/tutorial/how-to-configure-status-page", destination: "/docs/guides/how-to-configure-status-page", permanent: true, }, { source: "/docs/tutorial/how-to-setup-slack-agent", destination: "/docs/guides/how-to-setup-slack-agent", permanent: true, }, { source: "/docs/tutorial/how-to-create-private-location", destination: "/docs/guides/how-to-create-private-location", permanent: true, }, { source: "/docs/tutorial/how-to-import-status-page", destination: "/docs/guides/how-to-import-status-page", permanent: true, }, { source: "/docs/tutorial/how-to-manage-openstatus-with-terraform-cli", destination: "/docs/guides/how-to-manage-openstatus-with-terraform", permanent: true, }, { source: "/docs/guides/how-to-manage-openstatus-with-terraform-cli", destination: "/docs/guides/how-to-manage-openstatus-with-terraform", permanent: true, }, { source: "/docs/tutorial/how-to-connect-openstatus-to-claude-code", destination: "/docs/guides/how-to-connect-openstatus-to-your-agent", permanent: true, }, { source: "/compare/atlassian-statuspage", destination: "/guides/top-five-atlassian-statuspage-alternatives", permanent: true, }, { source: "/compare/instatus", destination: "/guides/top-five-instatus-alternatives", permanent: true, }, { source: "/compare/pingdom", destination: "/guides/top-five-pingdom-alternatives", permanent: true, }, { source: "/compare/uptime-kuma", destination: "/guides/hosted-uptime-kuma-alternative", permanent: true, }, ]; }, async rewrites() { return { beforeFiles: [ // `/openapi.json` is the path agents probe for an API description, and // they probe it on the site they were pointed at. The spec is generated // in apps/server, so proxy rather than keep a second copy in sync. // Host-scoped and ahead of the status-page rules below: those catch // every path on any host once `sp_mode=new` is set, and a customer's // custom domain has no business serving the openstatus API spec. { source: "/openapi.json", has: [{ type: "host", value: "(www\\.)?openstatus\\.dev" }], destination: "https://api.openstatus.dev/openapi.json", }, { source: "/openapi.yaml", has: [{ type: "host", value: "(www\\.)?openstatus\\.dev" }], destination: "https://api.openstatus.dev/openapi.yaml", }, { source: "/status-page/themes/:path*", destination: "https://www.stpg.dev/:path*", }, { source: "/:path*", has: [ { type: "host", value: "themes.openstatus.dev", }, ], destination: "https://www.stpg.dev/:path*", }, // New design: proxy app routes to external host with slug prefix { source: "/:path*", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "(?<slug>[^.]+)\\.(openstatus\\.dev|localhost)", }, ], destination: "https://:slug.stpg.dev/:path*", }, // Handle custom domains (e.g., status.mxkaske.dev) { source: "/:path((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?!.*\\.openstatus\\.dev$)(?!openstatus\\.dev$)$", }, ], destination: "https://www.stpg.dev/:path*", }, // enfore routes to avoid infinite redirects - https://github.com/vercel/vercel/issues/6126#issuecomment-823523122 // testing with https://validator.w3.org/feed/check.cgi { source: "/feed/rss", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?!.*\\.openstatus\\.dev$)(?!openstatus\\.dev$)$", }, ], destination: "https://www.stpg.dev/:domain/feed/rss", }, { source: "/feed/atom", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?!.*\\.openstatus\\.dev$)(?!openstatus\\.dev$)$", }, ], destination: "https://www.stpg.dev/:domain/feed/atom", }, { source: "/feed/rss", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?<domain>.+)$", }, ], destination: "https://www.stpg.dev/:domain/feed/rss", }, { source: "/feed/atom", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?<domain>.+)$", }, ], destination: "https://www.stpg.dev/:domain/feed/atom", }, { source: "/:path((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|badge|feed|events|monitors|protected|verify).*)", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?<domain>.+)$", }, ], destination: "https://www.stpg.dev/:domain*", }, { source: "/:path((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?<domain>.+)$", }, ], destination: "https://www.stpg.dev/:domain/:path*", }, // Handle API routes for custom domains { source: "/api/:path*", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?!.*\\.openstatus\\.dev$)(?!openstatus\\.dev$)(?<domain>.+)$", }, ], destination: "https://www.stpg.dev/api/:path*", }, // Handle static assets for custom domains { source: "/_next/:path*", has: [ { type: "cookie", key: "sp_mode", value: "new" }, { type: "host", value: "^(?!.*\\.openstatus\\.dev$)(?!openstatus\\.dev$)(?<domain>.+)$", }, ], destination: "https://www.stpg.dev/_next/:path*", }, // Markdown via .md URL suffix (e.g. /blog/foo.md โ /api/markdown/blog/foo) { source: "/:path*\\.md", destination: "/api/markdown/:path*", }, // Markdown content negotiation for AI tools { source: "/:path*", destination: "/api/markdown/:path*", has: [ { type: "header", key: "accept", value: "(.*)text/markdown(.*)", }, ], }, ], }; },};
// For detailed options, refer to the official documentation:// - Webpack plugin options: https://github.com/getsentry/sentry-webpack-plugin#options// - Next.js Sentry setup guide: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/const sentryConfig = { // Prevent log output unless running in a CI environment (helps reduce noise in logs) silent: !process.env.CI, org: "openstatus", project: "openstatus", authToken: process.env.SENTRY_AUTH_TOKEN,
// Upload a larger set of source maps for improved stack trace accuracy (increases build time) widenClientFileUpload: true,
// If set to true, transpiles Sentry SDK to be compatible with IE11 (increases bundle size) transpileClientSDK: false,
// Tree-shake Sentry logger statements to reduce bundle size webpack: { treeshake: { removeDebugLogging: true, }, },};
export default withSentryConfig(nextConfig, sentryConfig);