Appearance
Configuration ​
nuxt.config.ts ​
Top-level configuration for the Nuxt 4 app.
Key Settings ​
| Setting | Value | Notes |
|---|---|---|
compatibilityDate | '2025-06-30' | Nuxt 4 stable compatibility date |
future.compatibilityVersion | not set | Nuxt 4 file conventions enabled via compatibilityDate |
ssr | true | SSR enabled |
devtools.enabled | true | Nuxt DevTools on in dev |
devServer.host | '0.0.0.0' | Bind to all interfaces in dev |
devServer.port | 5000 | Default dev server port; overridden by PORT env var |
typescript.strict | true | TypeScript strict mode |
typescript.typeCheck | false | Run pnpm typecheck separately (faster dev HMR) |
features.inlineStyles | true in production | Inline critical CSS in production builds only |
Modules ​
ts
modules: [
'@vueuse/nuxt',
'@nuxt/icon',
'@nuxt/eslint',
'@pinia/nuxt',
]Auto-Import Configuration ​
ts
imports: {
dirs: [
'composables/**',
'utils/**',
'stores/**',
'constants/**',
]
}app/types/ is intentionally excluded — always import types explicitly with import type.
Component auto-import:
ts
components: [
{
path: '~/components',
pathPrefix: false, // <AssetCard> not <ComponentsAssetCard>
},
]Route Rules ​
Route rules are defined at the top level (not inside nitro):
ts
routeRules: {
'/': { prerender: true },
'/**': { headers: { 'Cache-Control': 's-maxage=60' } },
'/api/**': {
cors: true,
headers: { 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE' },
},
'/_nuxt/**': {
headers: { 'Cache-Control': 'public, max-age=31536000, immutable' },
},
}Vite ​
Key Vite settings:
ts
vite: {
logLevel: 'warn',
plugins: [vuetify({ autoImport: true })],
css: {
preprocessorOptions: {
scss: {
loadPaths: [
path.resolve('./app/assets/scss'),
path.resolve('./node_modules/vuetify/lib/styles'),
],
},
},
},
esbuild: {
target: 'esnext',
legalComments: 'none',
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
},
build: {
chunkSizeWarningLimit: 500,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules/vuetify')) return 'vuetify'
if (id.includes('node_modules/lodash-es')) return 'lodash'
if (id.includes('node_modules/chart.js') || id.includes('node_modules/vue-chartjs')) return 'charts'
},
},
},
},
}Vuetify 3 is loaded via vite-plugin-vuetify with tree-shaking enabled. Chunks are manually split for vuetify, lodash-es, and chart.js to keep the main bundle lean.
optimizeDeps.include pre-bundles: vue, pinia, @vueuse/core, vuetify, laravel-echo, pusher-js, dayjs, mitt, lodash-es, vue-chartjs, chart.js, @amplitude/analytics-browser, @amplitude/session-replay-browser, and others.
Nitro ​
ts
nitro: {
compressPublicAssets: process.env.NODE_ENV === 'production',
minify: process.env.NODE_ENV === 'production',
sourceMap: false,
prerender: { crawlLinks: false },
}When APP_ENV=local, Nitro adds a dev proxy that forwards /api/v1/ to API_BASE_URL. This lets the frontend use a relative path and lets Nitro inject the correct Origin header for local backend CORS.
Icon Module ​
ts
icon: {
mode: 'svg',
provider: 'server',
fallbackToApi: false,
customCollections: [
{
prefix: 'collage',
dir: './app/assets/icons',
normalizeIconName: false,
},
],
}Custom icons live in app/assets/icons/ and are accessed as collage:icon-name. Client-bundle scanning is only enabled for production builds.
Build Transpile ​
ts
build: {
transpile: ['vuetify', 'vue-chartjs', 'chart.js'],
}Vue Compiler ​
ts
vue: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('cropper-'),
},
}Treats <cropper-*> as native custom elements, avoiding Vue component lookup warnings.
App Head ​
Set from nuxt.config.ts at build time. Title comes from APP_NAME. Two Google fonts are loaded (Inter, Space Grotesk). The PWA manifest is served dynamically by server/routes/manifest.json.get.ts; no static favicon is set here — white-label hosts inject the favicon via useBrandingHead() at runtime.
Runtime Config ​
Server-only keys (never in client bundle):
ts
runtimeConfig: {
apiBaseUrlServer: '', // API_BASE_URL — absolute URL for SSR fetches
typesenseHost: '', // TYPESENSE_HOST
typesensePort: '443', // TYPESENSE_PORT
typesenseProtocol: 'https', // TYPESENSE_PROTOCOL
typesenseApiKey: '', // TYPESENSE_API_KEY
typesenseConnectionTimeout: '10', // TYPESENSE_CONNECTION_TIMEOUT
awsAccessKeyId: '', // AWS_ACCESS_KEY_ID
awsSecretAccessKey: '', // AWS_SECRET_ACCESS_KEY
awsBucket: '', // AWS_BUCKET
awsDefaultRegion: '', // AWS_DEFAULT_REGION
stripeSecretKey: '', // STRIPE_SECRET_KEY
stripeWebhookSecret: '', // STRIPE_WEBHOOK_SECRET
stripeWebhookEndpoint: '', // STRIPE_WEBHOOK_ENDPOINT
googleDriveClientSecret: '', // GOOGLE_DRIVE_CLIENT_SECRET
dropboxAppSecret: '', // DROPBOX_APP_SECRET
}Public keys (available in both server and client):
ts
runtimeConfig: {
public: {
appName: '', // APP_NAME
baseUrl: '', // BASE_URL
apiBaseUrl: '', // API_BASE_URL; '/api/v1/' when APP_ENV=local
originUrl: '', // ORIGIN_URL
backendUrl: '', // BACKEND_URL
damBaseUrl: undefined, // DAM_FRONTEND_URL
userPlaceHolderImg: undefined, // USER_PLACEHOLDER_IMG
googleAuthUrl: undefined, // GOOGLE_AUTH_URL
microsoftAuthUrl: undefined, // MICROSOFT_AUTH_URL
limitWorkspaceId: undefined, // LIMIT_WORKSPACE_ID
pusherKey: undefined, // PUSHER_KEY
pusherCluster: undefined, // PUSHER_CLUSTER
pusherAuthEndpoint: undefined, // PUSHER_AUTH_ENDPOINT
appEnv: undefined, // APP_ENV
insightsAccessPersons: undefined, // INSIGHTS_ACCESS_PERSONS
googleDriveClientId: undefined, // GOOGLE_DRIVE_CLIENT_ID
redirectUri: undefined, // REDIRECT_URI
dropboxOauthUri: undefined, // DROPBOX_OAUTH_URI
dropboxAuthVerificationUri: undefined, // DROPBOX_AUTH_VERIFICATION_URI
dropboxAppId: undefined, // DROPBOX_APP_ID
dropboxAuthRedirect: undefined, // DROPBOX_AUTH_REDIRECT
mobileAppDomain: undefined, // MOBILE_APP_DOMAIN
damUploadAutotagEnabled: undefined, // DAM_UPLOAD_AUTOTAG_ENABLED
supportBaseUrl: undefined, // SUPPORT_BASE_URL
isSupportPlatform: false, // IS_SUPPORT_PLATFORM (parsed: === 'true')
stripePublishableKey: undefined, // STRIPE_PUBLISHABLE_KEY
stripeCheckoutMode: 'prebuilt', // STRIPE_CHECKOUT_MODE
cname: '', // CNAME
zipDownloadUrl: '', // ZIP_DOWNLOAD_URL
https: '', // HTTPS
externalUploadLimit: '500MB', // EXTERNAL_UPLOAD_LIMIT
queryStaleTime: 30000, // NUXT_PUBLIC_QUERY_STALE_TIME (ms, overridable at runtime)
amplitudeApiKey: '', // AMPLITUDE_API_KEY
amplitudeSessionReplaySampleRate: '', // AMPLITUDE_SESSION_REPLAY_SAMPLE_RATE (0–1)
}
}All values default to empty string / undefined / false in the config. They are populated from environment variables at runtime (.env locally, deployment env vars in production).
Environment Variables ​
Copy .env.example to .env and fill in values for local development. Never commit .env.
Required ​
| Variable | Used by |
|---|---|
API_BASE_URL | Server-side fetches (apiBaseUrlServer); Nitro dev proxy target when APP_ENV=local |
APP_NAME | <title> tag and appName public config |
APP_ENV | Enables local dev proxy when set to local; exposed as appEnv |
BASE_URL | Public base URL of the app |
BACKEND_URL | DNS prefetch/preconnect hint in <head> |
PUSHER_KEY | Laravel Echo / Pusher client |
PUSHER_CLUSTER | Laravel Echo / Pusher client |
PUSHER_AUTH_ENDPOINT | Private channel authentication |
TYPESENSE_HOST | Nitro Typesense proxy |
TYPESENSE_API_KEY | Nitro Typesense proxy |
AWS_ACCESS_KEY_ID | Nitro S3 proxy |
AWS_SECRET_ACCESS_KEY | Nitro S3 proxy |
AWS_BUCKET | Nitro S3 proxy |
AWS_DEFAULT_REGION | Nitro S3 proxy |
STRIPE_PUBLISHABLE_KEY | Client-side Stripe.js |
STRIPE_SECRET_KEY | Server-side Stripe API |
AMPLITUDE_API_KEY | Amplitude analytics plugin |
Optional / Feature-specific ​
| Variable | Default | Used by |
|---|---|---|
PORT | 5000 | Dev server port |
TYPESENSE_PORT | 443 | Typesense proxy |
TYPESENSE_PROTOCOL | https | Typesense proxy |
TYPESENSE_CONNECTION_TIMEOUT | 10 | Typesense proxy |
AMPLITUDE_SESSION_REPLAY_SAMPLE_RATE | 0 | Session Replay sampling rate (0–1) |
STRIPE_WEBHOOK_SECRET | — | Stripe webhook verification |
STRIPE_WEBHOOK_ENDPOINT | — | Stripe webhook endpoint |
STRIPE_CHECKOUT_MODE | prebuilt | Stripe checkout mode |
GOOGLE_AUTH_URL | — | Google SSO |
GOOGLE_DRIVE_CLIENT_ID | — | Google Drive integration |
GOOGLE_DRIVE_CLIENT_SECRET | — | Google Drive integration (server-only) |
GOOGLE_SITE_VERIFICATION | — | Google Search Console meta tag |
MICROSOFT_AUTH_URL | — | Microsoft SSO |
REDIRECT_URI | — | OAuth redirect URI |
DROPBOX_APP_ID | — | Dropbox integration |
DROPBOX_APP_SECRET | — | Dropbox integration (server-only) |
DROPBOX_OAUTH_URI | — | Dropbox OAuth start URL |
DROPBOX_AUTH_VERIFICATION_URI | — | Dropbox OAuth callback |
DROPBOX_AUTH_REDIRECT | — | Dropbox redirect after auth |
DAM_FRONTEND_URL | — | Link back to the DAM frontend |
USER_PLACEHOLDER_IMG | — | Fallback avatar URL |
LIMIT_WORKSPACE_ID | — | Restricts UI to a single workspace |
INSIGHTS_ACCESS_PERSONS | — | Comma-separated user IDs with Analytics access |
ZIP_DOWNLOAD_URL | — | Backend ZIP download endpoint |
ORIGIN_URL | — | Override origin injected into user API calls |
MOBILE_APP_DOMAIN | — | Apple Smart App Banner app-argument |
DAM_UPLOAD_AUTOTAG_ENABLED | — | Enables AI auto-tagging on upload |
SUPPORT_BASE_URL | — | Link to support docs |
IS_SUPPORT_PLATFORM | false | Renders the support-team variant of the login layout |
CNAME | — | Custom domain CNAME |
HTTPS | — | Forces HTTPS redirects |
EXTERNAL_UPLOAD_LIMIT | 500MB | File size cap shown to external uploaders |
NUXT_PUBLIC_QUERY_STALE_TIME | 30000 | TanStack Query staleTime in ms (overridable at runtime without redeploy) |
LOG_LEVEL | — | Server log verbosity (0=silent … 3=info) |
CORS_ALLOWED_ORIGINS | — | Server CORS origin allowlist |
Server-only (not in runtimeConfig, used in server routes) ​
Some variables are consumed directly by Nitro server routes and do not flow through runtimeConfig. These include BOX_CLIENT_ID, BOX_CLIENT_SECRET, BOX_AUTH_REDIRECT, TWILIO_SID, TWILIO_TOKEN, OPENAI_KEY, OPENAI_PER_API_IMAGE_GENERATE_COUNT, and G_PLACES_API_KEY.
ESLint Configuration ​
eslint.config.ts provides project-wide lint rules. Run: pnpm lint
TypeScript Configuration ​
tsconfig.json is generated by Nuxt. Key settings:
strict: trueverbatimModuleSyntax: true— enforcesimport typefor type-only imports- Path alias
~→./app
Run pnpm typecheck to type-check without building.
Related Documentation ​
- Plugins —
runtimeConfigconsumption - Server API — server-side secret usage
- Styling — SCSS configuration