Project Architecture
Chapter 2
3 min read
To build a scalable portfolio, you need a solid directory foundation and a way to share design tokens (variables) across your entire application without repetitive imports.
For an App Router project using SCSS and MDX, a feature-based organization is the most efficient way to manage concerns.
src/
├── app/ # Next.js App Router (Pages & Layouts)
│ ├── portfolio/
│ │ ├── [slug]/ # Dynamic MDX project pages
│ │ └── page.tsx
│ ├── layout.tsx # Root layout (ThemeProvider resides here)
│ └── page.tsx # Homepage
├── components/ # Reusable UI components
│ ├── ui/ # Atomic components (Buttons, Cards)
│ └── layout/ # Shared components (Navbar, Footer)
├── styles/ # Global styling
│ ├── _variables.scss # Colors, spacing, and typography tokens
│ ├── _mixins.scss # Media queries and helper mixins
│ └── globals.scss # Global resets and CSS variables
├── content/ # MDX files for projects/case studies
└── lib/ # Shared utilities (clsx, analytics)To use your variables (like $color-crimson) in any .module.scss file without manually importing them every time, you can "inject" them via the Next.js configuration.
Modify your configuration to include the prependData option. This acts as a global header for every SCSS file processed:
/** @type {import('next').NextConfig} */
const nextConfig = {
sassOptions: {
// This automatically injects these files into every SCSS module
prependData: `
@use "@/styles/_variables.scss" as *;
@use "@/styles/_mixins.scss" as *;
`,
},
};
export default nextConfig;Now, you can use your variables and mixins directly. Next.js handles the "glue" behind the scenes:
// src/app/portfolio/Portfolio.module.scss
.heroWrapper {
background-color: $background-dark; // Auto-resolved variable
@include mobile { // Auto-resolved mixin
padding: 1rem;
}
.mainTitle {
color: $color-crimson;
}
}To ensure your .dark & selectors in SCSS function correctly, wrap your application in a ThemeProvider. This manages the dark class on the <html> tag to prevent theme-flicker during hydration.
// src/app/layout.tsx
import { ThemeProvider } from 'next-themes';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}