9 Commits

Author SHA1 Message Date
90104424b8 Add src/middleware.ts 2026-03-10 16:42:16 +00:00
1fbed390cd Update src/app/page.tsx 2026-03-10 16:42:15 +00:00
7e622afc00 Update src/app/layout.tsx 2026-03-10 16:42:14 +00:00
e76c8d11fd Add public/sw.js 2026-03-10 16:42:13 +00:00
dd1066e9a7 Add public/manifest.json 2026-03-10 16:42:13 +00:00
03ecd66c84 Add package.json.optimization 2026-03-10 16:42:12 +00:00
58285e428d Add next.config.js 2026-03-10 16:42:11 +00:00
0fae9dd3dc Add .browserslistrc 2026-03-10 16:42:10 +00:00
40f8acd29f Update src/app/page.tsx 2026-03-10 16:14:12 +00:00
7 changed files with 250 additions and 4 deletions

4
.browserslistrc Normal file
View File

@@ -0,0 +1,4 @@
last 2 versions
> 2%
not dead
upported by browserslist

73
next.config.js Normal file
View File

@@ -0,0 +1,73 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
// Enable SWC minification for smaller bundle
swcMinify: true,
// Enable experimental optimizations
experimental: {
optimizePackageImports: [
'lucide-react',
'@/components',
'@/providers'
]
},
// Image optimization
images: {
formats: ['image/webp', 'image/avif'],
remotePatterns: [
{
protocol: 'http',
hostname: 'img.b2bpic.net'
},
{
protocol: 'https',
hostname: '**'
}
]
},
// Enable compression
compress: true,
// Optimize production builds
productionBrowserSourceMaps: false,
// Generate static analysis
webpack: (config, { isServer }) => {
if (!isServer) {
config.optimization = {
...config.optimization,
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
lucide: {
test: /[\\/]node_modules[\\/](lucide-react)[\\/]/,
name: 'lucide',
priority: 10,
reuseExistingChunk: true
},
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20,
reuseExistingChunk: true
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
name: 'common'
}
}
}
};
}
return config;
}
};
module.exports = nextConfig;

14
package.json.optimization Normal file
View File

@@ -0,0 +1,14 @@
// Bundle optimization guidelines:
// 1. Tree-shaking enabled by default in Next.js with swcMinify: true
// 2. Dynamic imports for non-critical components
// 3. Code splitting configured in next.config.js for lucide-react, react, and common chunks
// 4. Service Worker caches on install and updates cache on fetch
// 5. Middleware applies proper cache headers:
// - Static assets: 1 year (immutable)
// - HTML pages: 1 hour + 1 day server cache
// - Dynamic content: 1 minute + 5 minutes server cache
// 6. Font optimization with display: 'swap' to avoid layout shift
// 7. DNS prefetch for external image CDN
// 8. Production source maps disabled
// 9. Image optimization with WebP and AVIF formats
// 10. Experimental optimizePackageImports for better tree-shaking

10
public/manifest.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "Sharanya Sasmal Portfolio", "short_name": "Portfolio", "description": "UI/UX & Graphic Designer Portfolio", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#000000", "orientation": "portrait-primary", "icons": [
{
"src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any"
},
{
"src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any"
}
]
}

68
public/sw.js Normal file
View File

@@ -0,0 +1,68 @@
const CACHE_VERSION = 'v1';
const CACHE_NAME = `portfolio-cache-${CACHE_VERSION}`;
const URLS_TO_CACHE = [
'/',
'/index.html',
'/manifest.json'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(URLS_TO_CACHE);
})
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((cacheName) => cacheName !== CACHE_NAME)
.map((cacheName) => caches.delete(cacheName))
);
})
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== 'GET') {
return;
}
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
return;
}
event.respondWith(
caches.match(request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(request)
.then((response) => {
if (!response || response.status !== 200 || response.type === 'error') {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache);
});
return response;
})
.catch(() => {
return caches.match('/index.html');
});
})
);
});

View File

@@ -12,13 +12,28 @@ import { Public_Sans } from "next/font/google";
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Sharanya Sasmal — UI/UX & Graphic Designer', title: 'Sharanya Sasmal — UI/UX & Graphic Designer',
description: 'Award-winning UI/UX and graphic designer crafting compelling digital experiences. Specializing in design systems, brand identity, and motion design.', description: 'Award-winning UI/UX and graphic designer crafting compelling digital experiences. Specializing in design systems, brand identity, and motion design.',
manifest: '/manifest.json',
viewport: 'width=device-width, initial-scale=1, maximum-scale=5, viewport-fit=cover',
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'Sharanya Sasmal Portfolio'
},
formatDetection: {
telephone: false
}
}; };
const publicSans = Public_Sans({ const publicSans = Public_Sans({
variable: "--font-public-sans", subsets: ["latin"], variable: "--font-public-sans", subsets: ["latin"],
display: 'swap',
fallback: ['system-ui', '-apple-system', 'sans-serif']
}); });
const inter = Inter({ const inter = Inter({
variable: "--font-inter", subsets: ["latin"], variable: "--font-inter", subsets: ["latin"],
display: 'swap',
fallback: ['system-ui', '-apple-system', 'sans-serif']
}); });
export default function RootLayout({ export default function RootLayout({
@@ -28,15 +43,35 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="//img.b2bpic.net" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
</head>
<ServiceWrapper> <ServiceWrapper>
<body className={`${publicSans.variable} ${inter.variable} antialiased`}> <body className={`${publicSans.variable} ${inter.variable} antialiased`}>
<Tag /> <Tag />
{children} {children}
<script <script
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: `${getVisualEditScript()}` __html: `${getVisualEditScript()}`
}} }}
/> />
<script
dangerouslySetInnerHTML={{
__html: `
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW registered'))
.catch(err => console.log('SW registration failed'));
});
}
`
}}
/>
</body> </body>
</ServiceWrapper> </ServiceWrapper>
</html> </html>

42
src/middleware.ts Normal file
View File

@@ -0,0 +1,42 @@
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Set caching headers for static assets
if (request.nextUrl.pathname.startsWith('/images/') ||
request.nextUrl.pathname.startsWith('/fonts/') ||
request.nextUrl.pathname.match(/\.(js|css|woff|woff2)$/)) {
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
}
// Set caching for HTML pages
else if (request.nextUrl.pathname.match(/\/$/) || request.nextUrl.pathname.match(/\.html$/)) {
response.headers.set('Cache-Control', 'public, max-age=3600, s-maxage=86400');
}
// Default caching for dynamic content
else {
response.headers.set('Cache-Control', 'public, max-age=60, s-maxage=300');
}
// Set security headers
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public (public folder)
*/
'/((?!api|_next/static|_next/image|favicon.ico|public).*)',
],
};