admin/next.config.js
alisaza 840c328819 refactor: remove unused icons and dependencies
- Deleted several unused icon components including AddDocumentIcon, ArrowRightIcon, AttachIcon, and others to clean up the codebase.
- Removed the 'arcaptcha-react' package from package.json and next.config.js as it is no longer needed.
- This cleanup helps improve project maintainability and reduces bundle size.
2026-09-05 16:40:50 +03:30

213 lines
7.5 KiB
JavaScript

const packageJson = require('./package.json')
const { withSentryConfig } = require('@sentry/nextjs')
const isProduction = process.env.NODE_ENV === 'production'
/**
* When the browser talks to the API on a different host than the Next app
* (LAN Nest, or https://ghabilee.ir from localhost), CORS / SameSite cookies
* break. Point NEXT_PUBLIC_API_URL at this Next origin (e.g.
* http://localhost:3008/api/v1) and set API_PROXY_TARGET to the real backend
* so /api/v1 stays same-origin for the browser.
*
* Nest still names/flags the httpOnly refresh cookie from *its* NODE_ENV
* (`__Host-ghabilee_refresh`+Secure in production, `ghabilee_refresh` on
* local HTTP). Rewriting local HTTP Next to production Nest can therefore
* emit a Secure `__Host-` cookie the browser may reject. Prefer local Nest
* when testing login/refresh. See docs/reference/environment-variables.md.
*/
const apiProxyTarget = (process.env.API_PROXY_TARGET || '').replace(/\/+$/, '')
/** Path prefix for reverse-proxy deploys (e.g. `/ghabilee`). Empty = site root. */
const rawBasePath = (process.env.NEXT_PUBLIC_BASE_PATH || '').trim()
const basePath = rawBasePath.replace(/\/+$/, '') || undefined
const swScope = basePath ? `${basePath}/` : '/'
// Preserve the search equity of URLs that existed before the public SEO
// routes were introduced. Specific article migrations must come before the
// generic /blogs/:slug rule so removed articles land on the closest useful
// replacement instead of a soft-404.
const retiredCategorySlugRedirects = [
['psychology', 'learning-experience'],
['book-reading', 'conversation-connection'],
['open-conversation', 'conversation-connection'],
['walking', 'sports-adventure'],
['pottery', 'learning-experience'],
['games', 'games-entertainment'],
['educational-workshop', 'learning-experience'],
['art-creativity', 'arts-culture'],
['health-wellness', 'sports-adventure'],
['film-documentary', 'arts-culture'],
['daily-challenges', 'conversation-connection'],
['philosophy-thinking', 'conversation-connection'],
['experience-sharing', 'conversation-connection'],
['futures-studies', 'conversation-connection'],
['board-games', 'games-entertainment'],
['active-team-games', 'games-entertainment'],
['puzzle-mind-games', 'games-entertainment'],
['fun-sensory-games', 'games-entertainment'],
].flatMap(([from, to]) => [
{ source: `/category/${from}`, destination: `/category/${to}`, permanent: true },
{ source: `/category/${from}/:path*`, destination: `/category/${to}/:path*`, permanent: true },
])
const retiredCitySlugRedirects = [
['thran', 'tehran'],
['mshd', 'mashhad'],
['asfhan', 'isfahan'],
['syraz', 'shiraz'],
['tbryz', 'tabriz'],
].flatMap(([from, to]) => [
{ source: `/city/${from}`, destination: `/city/${to}`, permanent: true },
{ source: `/category/:categorySlug/city/${from}`, destination: `/category/:categorySlug/city/${to}`, permanent: true },
])
const legacySeoRedirects = [
{ source: '/karagah-ravanshenasi', destination: '/category/learning-experience', permanent: true },
{ source: '/varzesh-goroohi', destination: '/category/sports-adventure', permanent: true },
{ source: '/honar-va-khalaghiat', destination: '/category/arts-culture', permanent: true },
{ source: '/bazi-va-sargarmi', destination: '/category/games-entertainment', permanent: true },
{ source: '/shabake-sazi-ejtemai', destination: '/category/conversation-connection', permanent: true },
{
source: '/blogs/goroohhaye-piyaderoyi-iran',
destination: '/blog/hiking-outdoor-group-guide',
permanent: true,
},
{ source: '/blogs/mazayaye-karagah-khodshanasi', destination: '/category/learning-experience', permanent: true },
{ source: '/blogs', destination: '/blog', permanent: true },
{ source: '/blogs/:slug', destination: '/blog/:slug', permanent: true },
...retiredCategorySlugRedirects,
...retiredCitySlugRedirects,
]
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
logging: false,
output: 'standalone',
...(basePath ? { basePath, assetPrefix: basePath } : {}),
allowedDevOrigins: ['192.168.100.51', '192.168.100.52'],
transpilePackages: ['mapir-react-component', 'react-mapbox-gl'],
env: {
NEXT_PUBLIC_MAP_API_KEY: process.env.MAP_API_KEY || process.env.NEXT_PUBLIC_MAP_API_KEY || '',
NEXT_PUBLIC_APP_VERSION: packageJson.version,
NEXT_PUBLIC_VAPID_PUBLIC_KEY: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || process.env.VAPID_PUBLIC_KEY || '',
NEXT_PUBLIC_BASE_PATH: basePath || '',
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'ghabilee.ir',
pathname: '/uploads/**',
},
{
protocol: 'https',
hostname: 'cdn.ghabilee.ir',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'file-dev.ghabilee.org',
pathname: '/**',
},
{
protocol: 'http',
hostname: '192.168.100.51',
port: '3000',
pathname: '/**',
},
{
protocol: 'http',
hostname: '65.21.166.55',
pathname: '/**',
},
],
},
async redirects() {
return legacySeoRedirects
},
async rewrites() {
if (!apiProxyTarget) return []
return [
{
source: '/api/v1/:path*',
destination: `${apiProxyTarget}/api/v1/:path*`,
},
{
source: '/socket.io/:path*',
destination: `${apiProxyTarget}/socket.io/:path*`,
},
]
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=(self), payment=()' },
...(isProduction ? [{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' }] : []),
...(isProduction ? [{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' }] : []),
],
},
{
source: '/sw.js',
headers: [
{ key: 'Cache-Control', value: 'no-cache, no-store, must-revalidate' },
{ key: 'Service-Worker-Allowed', value: swScope },
],
},
{
source: '/manifest.webmanifest',
headers: [{ key: 'Cache-Control', value: 'public, max-age=0, must-revalidate' }],
},
]
},
turbopack: {
root: __dirname,
},
}
const sentryAuthToken = (process.env.SENTRY_AUTH_TOKEN || '').trim()
module.exports = withSentryConfig(nextConfig, {
org: process.env.SENTRY_ORG || undefined,
project: process.env.SENTRY_PROJECT || undefined,
authToken: sentryAuthToken || undefined,
// Source maps upload only when a CI/build auth token is present.
sourcemaps: {
disable: !sentryAuthToken,
},
silent: true,
// Tunnel through our origin so ad-blockers don't drop browser events.
tunnelRoute: '/monitoring',
widenClientFileUpload: Boolean(sentryAuthToken),
// Replay is intentionally disabled in sentry.client.config.ts. Remove its
// browser-only helpers plus SDK debug statements while preserving tracing.
bundleSizeOptimizations: {
excludeDebugStatements: true,
excludeReplayIframe: true,
excludeReplayShadowDom: true,
excludeReplayWorker: true,
},
webpack: {
treeshake: {
excludeReplayCompressionWorker: true,
excludeReplayIframe: true,
excludeReplayShadowDOM: true,
removeDebugLogging: true,
},
automaticVercelMonitors: false,
},
})