Animation-heavy personal portfolio on Next.js: a custom cursor, a page-transition loader, and canvas backgrounds.
canvas-animationscss-modulesdark-themehandcodednextjspersonal-websiteportfolioreact
1/** @type {import('next').NextConfig} */
2const isDev = process.env.NODE_ENV !== "production";
3
4const securityHeaders = [
5 { key: "X-Content-Type-Options", value: "nosniff" },
6 { key: "X-Frame-Options", value: "SAMEORIGIN" },
7 { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
8 {
9 key: "Permissions-Policy",
10 value: "camera=(), microphone=(), geolocation=(), interest-cohort=()",
11 },
12 {
13 key: "Content-Security-Policy",
14 value: [
15 "default-src 'self'",
16 // unsafe-eval only in dev: Next's dev runtime needs it, prod does not.
17 // analytics.bythewood.me hosts the collector script and receives its
18 // beacons.
19 `script-src 'self' 'unsafe-inline'${
20 isDev ? " 'unsafe-eval'" : ""
21 } https://analytics.bythewood.me`,
22 "style-src 'self' 'unsafe-inline'",
23 "img-src 'self' data: blob:",
24 "font-src 'self' data:",
25 "connect-src 'self' blob: https://analytics.bythewood.me",
26 "manifest-src 'self'",
27 "base-uri 'self'",
28 "form-action 'self'",
29 "frame-ancestors 'self'",
30 ].join("; "),
31 },
32];
33
34const nextConfig = {
35 devIndicators: {
36 position: "bottom-right",
37 },
38 allowedDevOrigins: ["127.0.0.1"],
39 async headers() {
40 return [
41 {
42 source: "/:path*",
43 headers: securityHeaders,
44 },
45 ];
46 },
47};
48
49module.exports = nextConfig;