mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 11:55:05 +03:00
Nonexistent paths return a real 404 everywhere, but always with an HTML body, even when the client asked for markdown via `Accept: text/markdown` or a `.md` suffix. Middleware can't fix this: it gates on a static slug allowlist and can't know a path will 404. I added two `fallback` rewrites (`.md` suffix; Accept header containing `text/markdown` or `text/*`) that run only after every route has failed to match and route the request to a small `md-404` handler returning a short markdown 404 pointing at /docs, /sitemap.xml, and /llms.txt. Real pages are structurally unaffected. **Note:** `lib/rewrites.js` is untouched (the plain rewrites array became the `afterFiles` phase), so #49587 merges independently. I updated next.config.test.ts's rewrites assertion for the phased shape; it now also pins the two fallback rules. ## To test I verified on the Vercel preview: - [x] `curl -s -D - -H "Accept: text/markdown" <preview>/definitely-not-a-page` (404, `Content-Type: text/markdown`, body with the three pointers) - [x] Same URL with a browser Accept header (existing HTML 404, unchanged) - [x] `curl -s -D - <preview>/definitely-not-a-page.md` (markdown 404) - [x] `curl -s -D - -H "Accept: text/markdown" <preview>/auth` (200 markdown, unchanged) and `<preview>/support` (200 HTML, unchanged) - [x] `/homepage.md` still 308s to `/index.md` (redirects phase wins); `Accept: text/*` gets the markdown 404, matching real-page negotiation Known boundary: `/changelog/<unknown>` keeps the HTML 404 body (pages-router `fallback: 'blocking'` routes take priority over fallback rewrites per Next docs); the status is still 404, verified on the preview. ## Linear - fixes GROWTH-1142 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added Markdown-formatted 404 responses for unmatched documentation and `.md` page requests. - Included helpful documentation links in not-found responses. - Requests that explicitly accept Markdown now receive a consistent Markdown response. - Added appropriate response headers for security, caching, and content variation. - **Bug Fixes** - Improved routing for unmatched Markdown paths, ensuring they are handled by the appropriate not-found response instead of returning an unexpected format. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
283 lines
8.3 KiB
JavaScript
283 lines
8.3 KiB
JavaScript
import { remarkCodeHike } from '@code-hike/mdx'
|
|
import bundleAnalyzer from '@next/bundle-analyzer'
|
|
import nextMdx from '@next/mdx'
|
|
import { withSentryConfig } from '@sentry/nextjs'
|
|
import codeHikeTheme from 'config/code-hike.theme.json' with { type: 'json' }
|
|
import rehypeSlug from 'rehype-slug'
|
|
import remarkGfm from 'remark-gfm'
|
|
|
|
import redirects from './lib/redirects.js'
|
|
import remotePatterns from './lib/remotePatterns.js'
|
|
import rewrites from './lib/rewrites.js'
|
|
|
|
const withMDX = nextMdx({
|
|
extension: /\.mdx?$/,
|
|
options: {
|
|
remarkPlugins: [
|
|
[
|
|
remarkCodeHike,
|
|
{
|
|
theme: codeHikeTheme,
|
|
lineNumbers: true,
|
|
showCopyButton: true,
|
|
},
|
|
],
|
|
remarkGfm,
|
|
],
|
|
rehypePlugins: [rehypeSlug],
|
|
// This is required for `MDXProvider` component
|
|
providerImportSource: '@mdx-js/react',
|
|
},
|
|
})
|
|
|
|
const withBundleAnalyzer = bundleAnalyzer({
|
|
enabled: process.env.ANALYZE === 'true',
|
|
})
|
|
|
|
/**
|
|
* @type {import('next').NextConfig}
|
|
*/
|
|
const nextConfig = {
|
|
basePath: '',
|
|
assetPrefix: getAssetPrefix(),
|
|
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
|
|
trailingSlash: false,
|
|
transpilePackages: [
|
|
'ui',
|
|
'ui-patterns',
|
|
'common',
|
|
'shared-data',
|
|
'icons',
|
|
'api-types',
|
|
'marketing',
|
|
// needed to make the octokit packages work in /changelog
|
|
'@octokit/plugin-paginate-graphql',
|
|
],
|
|
experimental: {
|
|
// needed to make the octokit packages work in /changelog
|
|
esmExternals: 'loose',
|
|
},
|
|
/**
|
|
* Exclude huge directories from being traced into serverless functions
|
|
* to avoid the max size limit for Serverless Functions on Vercel:
|
|
* https://vercel.com/guides/troubleshooting-function-250mb-limit
|
|
*/
|
|
outputFileTracingExcludes: {
|
|
'*': [
|
|
// Next.js build artifacts
|
|
'.next/cache/**/*',
|
|
'.next/static/**/*',
|
|
// Static assets
|
|
'public/**/*',
|
|
],
|
|
},
|
|
/**
|
|
* /llms.txt enumerates top-level guide directories at runtime via fs.readdir
|
|
* on apps/docs/content/guides and reads each <dir>.mdx for its frontmatter
|
|
* title. /llms-full.txt recursively reads the generated .md files under
|
|
* apps/docs/public/markdown/. Both directories live in a sibling app, so
|
|
* they have to be explicitly traced into this lambda's bundle.
|
|
*/
|
|
outputFileTracingIncludes: {
|
|
'/llms.txt': ['../docs/content/guides/*'],
|
|
'/llms-full.txt': ['../docs/public/markdown/guides/**/*.md'],
|
|
},
|
|
reactStrictMode: true,
|
|
images: {
|
|
dangerouslyAllowSVG: false,
|
|
remotePatterns,
|
|
},
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: '/changelog-rss.xml',
|
|
headers: [
|
|
{ key: 'Content-Type', value: 'application/rss+xml; charset=utf-8' },
|
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
{ key: 'Cache-Control', value: 'public, s-maxage=900, stale-while-revalidate=900' },
|
|
],
|
|
},
|
|
{
|
|
source: '/changelog-rss/:slug.xml',
|
|
headers: [
|
|
{ key: 'Content-Type', value: 'application/rss+xml; charset=utf-8' },
|
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
{ key: 'Cache-Control', value: 'public, s-maxage=900, stale-while-revalidate=900' },
|
|
],
|
|
},
|
|
{
|
|
source: '/changelog/:slug.md',
|
|
headers: [
|
|
{ key: 'Content-Type', value: 'text/markdown; charset=utf-8' },
|
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
{ key: 'Cache-Control', value: 'public, s-maxage=900, stale-while-revalidate=900' },
|
|
{ key: 'Vary', value: 'Accept' },
|
|
],
|
|
},
|
|
{
|
|
source: '/changelog.md',
|
|
headers: [
|
|
{ key: 'Content-Type', value: 'text/markdown; charset=utf-8' },
|
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
{ key: 'Cache-Control', value: 'public, s-maxage=900, stale-while-revalidate=900' },
|
|
{ key: 'Vary', value: 'Accept' },
|
|
],
|
|
},
|
|
{
|
|
source: '/',
|
|
headers: [
|
|
{
|
|
key: 'Link',
|
|
value: '</.well-known/api-catalog>; rel="api-catalog"',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: '/.well-known/api-catalog',
|
|
headers: [
|
|
{ key: 'content-type', value: 'application/linkset+json' },
|
|
{ key: 'access-control-allow-origin', value: '*' },
|
|
],
|
|
},
|
|
{
|
|
source: '/.well-known/vercel/flags',
|
|
headers: [
|
|
{
|
|
key: 'content-type',
|
|
value: 'application/json',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: '/favicon/:slug*',
|
|
headers: [{ key: 'cache-control', value: 'public, max-age=86400' }],
|
|
},
|
|
{
|
|
source: '/(.*)',
|
|
headers: [
|
|
{
|
|
key: 'Strict-Transport-Security',
|
|
value:
|
|
process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && process.env.VERCEL === '1'
|
|
? 'max-age=31536000; includeSubDomains; preload'
|
|
: '',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: '/(docs|blog)/:path*',
|
|
headers: [
|
|
{
|
|
key: 'X-Robots-Tag',
|
|
value: 'all',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: '/dashboard/:path*',
|
|
headers: [
|
|
{
|
|
key: 'X-Robots-Tag',
|
|
value: 'noindex',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: '/enterprise-terms',
|
|
headers: [
|
|
{
|
|
key: 'X-Robots-Tag',
|
|
value: 'noindex, nofollow',
|
|
},
|
|
],
|
|
},
|
|
]
|
|
},
|
|
async rewrites() {
|
|
return {
|
|
afterFiles: rewrites,
|
|
fallback: [
|
|
{
|
|
source: '/:path(.*\\.md)',
|
|
destination: '/api-v2/md-404/:path',
|
|
},
|
|
{
|
|
source: '/:path*',
|
|
has: [{ type: 'header', key: 'accept', value: '.*text/(markdown|\\*).*' }],
|
|
destination: '/api-v2/md-404/:path*',
|
|
},
|
|
],
|
|
}
|
|
},
|
|
async redirects() {
|
|
// For /docs/guides/ redirects, auto-generate .md variants so renamed/deleted pages
|
|
// redirect correctly when fetched as markdown
|
|
const docsMdRedirects = redirects
|
|
.filter(
|
|
(r) =>
|
|
r.source.startsWith('/docs/guides/') &&
|
|
typeof r.destination === 'string' &&
|
|
r.destination.startsWith('/')
|
|
)
|
|
.map((r) => ({ ...r, source: `${r.source}.md`, destination: `${r.destination}.md` }))
|
|
|
|
return [...docsMdRedirects, ...redirects]
|
|
},
|
|
typescript: {
|
|
// On previews, typechecking is run via GitHub Action only for efficiency
|
|
// On production, we turn it on to prevent errors from conflicting PRs getting into
|
|
// prod
|
|
ignoreBuildErrors: process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' ? false : true,
|
|
},
|
|
eslint: {
|
|
// We are already running linting via GH action, this will skip linting during production build on Vercel.
|
|
ignoreDuringBuilds: true,
|
|
},
|
|
}
|
|
|
|
// next.config.js.
|
|
const configExport = () => {
|
|
const plugins = [withMDX, withBundleAnalyzer]
|
|
return plugins.reduce((acc, next) => next(acc), nextConfig)
|
|
}
|
|
|
|
export default withSentryConfig(configExport, {
|
|
// For all available options, see:
|
|
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
|
|
|
|
org: 'supabase',
|
|
project: 'www',
|
|
|
|
// Only print logs for uploading source maps in CI
|
|
silent: !process.env.CI,
|
|
|
|
// For all available options, see:
|
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
|
|
|
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
|
widenClientFileUpload: true,
|
|
|
|
// Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
|
// This can increase your server load as well as your hosting bill.
|
|
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
|
// side errors will fail.
|
|
// tunnelRoute: "/monitoring",
|
|
|
|
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
|
disableLogger: true,
|
|
})
|
|
|
|
function getAssetPrefix() {
|
|
// If not force enabled, but not production env, disable CDN
|
|
if (process.env.FORCE_ASSET_CDN !== '1' && process.env.VERCEL_ENV !== 'production') {
|
|
return undefined
|
|
}
|
|
|
|
// Force disable CDN
|
|
if (process.env.FORCE_ASSET_CDN === '-1') {
|
|
return undefined
|
|
}
|
|
|
|
return `https://frontend-assets.supabase.com/${process.env.SITE_NAME}/${process.env.VERCEL_GIT_COMMIT_SHA.substring(0, 12)}`
|
|
}
|