Files
librechat.ai/next.config.mjs
Marco Beretta e48cd86b2b refactor: remove Nextra, migrate to App Router, redesign Toolkit (#531)
* refactor: remove Nextra shims, migrate pages/ to app router, upgrade ESLint to v9

- Remove all nextra-shims and legacy pages/ directory
- Migrate subscribe, unsubscribe, and toolkit pages to app router
- Restructure config docs with guide-first setup steps and Tabs components
- Rewrite Docker install, OpenRouter, and custom endpoints docs
- Add Quick Start guide, Google Search docs, and image generation cross-links
- Update .gitignore

* feat: redesign toolkit and integrate into docs sidebar

Move credentials generator and YAML validator into the docs under
a new "Tools > Toolkit" sidebar section. Old /toolkit routes redirect
to /docs/toolkit.

Credentials generator: 2-column field grid, per-field copy buttons,
copy-all as .env block, empty state placeholder, design system tokens.

YAML validator: full-width theme-aware Ace Editor (chrome/twilight),
drag-and-drop overlay, result banners with icons, clear button,
no print margin.

Remove unused .error-marker and .custom-btn from style.css.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-20 17:43:32 -04:00

201 lines
5.8 KiB
JavaScript

import { start } from 'fumadocs-mdx/next';
import NextBundleAnalyzer from '@next/bundle-analyzer';
import { resolve } from 'path';
const withBundleAnalyzer = NextBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
/**
* Start the Fumadocs MDX server which generates .source/ files
* from content/ directory. This runs separately from the webpack loader.
*/
if (process.env._FUMADOCS_MDX !== '1') {
process.env._FUMADOCS_MDX = '1';
void start(process.env.NODE_ENV === 'development', 'source.config.ts', '.source');
}
/**
* CSP headers
* img-src https to allow loading images from SSO providers
* 'unsafe-inline' is required for inline styles and Next.js script injection
*/
const cspHeader = `
default-src 'self' https: wss:;
script-src 'self' 'unsafe-inline' ${process.env.NODE_ENV === 'development' ? "'unsafe-eval'" : ''} https:;
style-src 'self' 'unsafe-inline' https:;
img-src 'self' https: blob: data:;
media-src 'self' https: blob: data:;
font-src 'self' https:;
frame-src 'self' https:;
worker-src 'self' blob:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
block-all-mixed-content;
`;
const nonPermanentRedirects = [
['/discord', 'https://discord.librechat.ai'],
['/demo', 'https://chat.librechat.ai'],
['/issue', 'https://github.com/danny-avila/LibreChat/issues/new/choose'],
['/new-issue', 'https://github.com/danny-avila/LibreChat/issues/new/choose'],
['/issues', 'https://github.com/danny-avila/LibreChat/issues'],
['/gh-support', 'https://github.com/danny-avila/LibreChat/discussions/categories/support'],
['/gh-discussions', 'https://github.com/danny-avila/LibreChat/discussions'],
['/roadmap', '/blog/2026-02-18_2026_roadmap'],
['/features', '/docs/features'],
['/docs/configuration/librechat_yaml/ai_endpoints/azure', '/docs/configuration/azure'],
['/docs/user_guides/artifacts', '/docs/features/artifacts'],
['/docs/user_guides/fork', '/docs/features/fork'],
['/docs/user_guides/authentication', '/docs/features/authentication'],
['/docs/user_guides/mod_system', '/docs/features/mod_system'],
['/docs/user_guides/search', '/docs/features/search'],
['/docs/user_guides/import_convos', '/docs/features/import_convos'],
['/docs/user_guides/password_reset', '/docs/features/password_reset'],
['/docs/user_guides/rag_api', '/docs/features/rag_api'],
['/docs/user_guides/plugins', '/docs/features/agents'],
['/docs/features/plugins', '/docs/features/agents'],
['/docs/configuration/librechat_yaml/setup', '/docs/configuration/librechat_yaml'],
['/toolkit/yaml_checker', '/toolkit/yaml-checker'],
['/toolkit/creds_generator', '/toolkit/creds-generator'],
];
/** @type {import('next').NextConfig} */
const config = {
typescript: {
ignoreBuildErrors: true,
},
turbopack: {},
pageExtensions: ['mdx', 'md', 'jsx', 'js', 'tsx', 'ts'],
webpack(webpackConfig, options) {
/**
* Fumadocs MDX loader: only applied to content/ directory files.
* These are processed by fumadocs-mdx for the app/ router docs.
*/
webpackConfig.module.rules.push({
test: /\.mdx?$/,
include: [resolve(process.cwd(), 'content')],
use: [
options.defaultLoaders.babel,
{
loader: 'fumadocs-mdx/loader-mdx',
options: {
configPath: 'source.config.ts',
outDir: '.source',
},
},
],
});
/**
* MDX loader for components/ directory files.
* These are MDX files imported directly as React components
* (e.g. changelog content, repeated sections).
*/
webpackConfig.module.rules.push({
test: /\.mdx?$/,
include: [resolve(process.cwd(), 'components')],
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: {
providerImportSource: resolve(process.cwd(), 'lib/mdx-provider.ts'),
},
},
],
});
return webpackConfig;
},
transpilePackages: ['react-tweet', 'geist'],
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'static.librechat.ai',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'github.com',
port: '',
pathname: '/{user-attachments,danny-avila}/**',
},
{
protocol: 'https',
hostname: 'firebasestorage.googleapis.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'www.librechat.ai',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'librechat.ai',
port: '',
pathname: '/**',
},
],
},
headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'x-frame-options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'autoplay=(self), fullscreen=(self), microphone=()',
},
],
},
{
source: '/:path((?!api).*)*',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replaceAll('\n', ''),
},
],
},
];
},
async rewrites() {
return [
{
source: '/docs/:path*.mdx',
destination: '/llms.mdx/docs/:path*',
},
];
},
redirects: async () => [
...nonPermanentRedirects.map(([source, destination]) => ({
source,
destination,
permanent: false,
})),
],
};
export default withBundleAnalyzer(config);