From e94dec0036decaa037a4625bbd3d291bd6d5aec0 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Sun, 19 Apr 2026 09:48:53 +0000 Subject: [PATCH] Performance improvements and Dockerfile update --- .gitignore | 1 + Dockerfile | 53 ++++++------- Makefile | 21 +++++ ansible/vars/default.yml | 2 +- eslint.config.mjs | 4 +- src/app/__root.tsx | 7 +- src/app/about.tsx | 114 ++++++++++++++------------- src/app/index.tsx | 122 +++++++++++++++-------------- src/components/BannerSlider.tsx | 30 +++---- src/components/DesktopFooter.tsx | 54 ++++++------- src/components/Header.tsx | 104 ++++++++++++------------ src/components/MobileFooter.tsx | 46 +++++------ src/components/ProductCategory.tsx | 6 +- src/components/Section.tsx | 6 +- src/components/SocialMedia.tsx | 32 ++++---- src/vite-env.d.ts | 1 + tsconfig.json | 7 +- 17 files changed, 315 insertions(+), 295 deletions(-) create mode 100644 src/vite-env.d.ts diff --git a/.gitignore b/.gitignore index 5ef6a52..a0c0d54 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # production /build +.output/ # misc .DS_Store diff --git a/Dockerfile b/Dockerfile index 0142d39..80122cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,6 @@ FROM node:22-alpine AS base -ARG NEXT_PUBLIC_INSTAGRAM_ACCESS_TOKEN - -# Install dependencies only when needed +# Stage 1: Install dependencies FROM base AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat @@ -18,54 +16,47 @@ RUN \ fi -# Rebuild the source code only when needed +# Stage 2: Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry during the build. -ENV NEXT_TELEMETRY_DISABLED=1 +# Environment variables needed during build time +ARG INSTAGRAM_ACCESS_TOKEN +ENV INSTAGRAM_ACCESS_TOKEN=$INSTAGRAM_ACCESS_TOKEN RUN \ - if [ -f yarn.lock ]; then yarn run build; \ + if [ -f yarn.lock ]; then yarn build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ - else echo "Lockfile not found." && exit 1; \ + else npm run build; \ fi -# Production image, copy all the files and run next + +# Stage 3: Production image, copy all the files and run nitro FROM base AS runner WORKDIR /app ENV NODE_ENV=production -# Uncomment the following line in case you want to disable telemetry during runtime. -ENV NEXT_TELEMETRY_DISABLED=1 +# Set host to 0.0.0.0 to ensure it's accessible outside the container +ENV HOST=0.0.0.0 +ENV PORT=3000 RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs +RUN adduser --system --uid 1001 nitrojs -COPY --from=builder /app/public ./public +# Copy the build output from the builder stage +# TanStack Start/Nitro builds into the .output directory +COPY --from=builder --chown=nitrojs:nodejs /app/.output ./.output -# Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:nodejs .next - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/public ./public -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - -USER nextjs +USER nitrojs EXPOSE 3000 -ENV PORT=3000 +# Health check to ensure the server is responding +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1 -# server.js is created by next build from the standalone output -# https://nextjs.org/docs/pages/api-reference/next-config-js/output -ENV HOSTNAME="0.0.0.0" -CMD ["node", "server.js"] \ No newline at end of file +# server.mjs is created by nitro build +CMD ["node", ".output/server/index.mjs"] \ No newline at end of file diff --git a/Makefile b/Makefile index 3b1c9a7..8747722 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,26 @@ build-production: ## Build the production docker image. --platform linux/amd64,linux/arm64/v8 \ --tag registry.tanshu.com/mozimo:latest \ $(if $(TAG),--tag registry.tanshu.com/mozimo:$(TAG)) \ + --pull \ --push \ git@github.com:tanshu/mozimo.git + + +.PHONY: build-check +build-check: ## Multi-arch build without push (compile check) + @docker buildx build \ + --platform linux/amd64,linux/arm64/v8 \ + --tag mozimo:test \ + --pull \ + --progress=plain \ + git@git.tanshu.com:tanshu/mozimo.git + +.PHONY: build-check-local +build-check-local: ## Multi-arch build without push (compile check) + @git archive --format=tar HEAD | docker buildx build \ + --platform linux/amd64 \ + --tag barker:test \ + --pull \ + --progress=plain \ + --load \ + - diff --git a/ansible/vars/default.yml b/ansible/vars/default.yml index 02f9e42..089c5c4 100644 --- a/ansible/vars/default.yml +++ b/ansible/vars/default.yml @@ -16,7 +16,7 @@ docker_image: "{{ registry }}/{{ title }}:{{ tag }}" docker_container: "{{ title }}" docker_port: 3000 -instagram_token: IGQWRQSUY4b3RQWU9ZARHlVVUFDaWxJZAWFOUVllM0NxMk1zSHJ5X2JGc2dpRUxyTjBaeDNhUm0yaFZAQeUotVU9VUmFEQkJxU25CdFp3bURSZAGtnLXhCZAHVId21SWUNiNjVzc0pjZAlhPNDRxTDFzZAEQ0ZAXoyVlpUaHcZD +instagram_token: IGQWRhYzlibzE4VEpNYzZA2eHEtRkVLUXJiT3NyVU9mNjlNRzM1cmd2NzY0SGxtX1ZAVVGVwb1dkbFl4VlVWaW02aFhEakQzeUp3NjBCSktRR2lQYlgwV3UwMG1seTFsWDBMSzZAHejNYdlE5RFMzU0tqbS1OVEttN0UZD caddy_container: caddy diff --git a/eslint.config.mjs b/eslint.config.mjs index c85fb67..b2a1eb8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,8 +9,6 @@ const compat = new FlatCompat({ baseDirectory: __dirname, }); -const eslintConfig = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), -]; +const eslintConfig = [{}]; export default eslintConfig; diff --git a/src/app/__root.tsx b/src/app/__root.tsx index c497fec..d686505 100644 --- a/src/app/__root.tsx +++ b/src/app/__root.tsx @@ -1,6 +1,9 @@ import { createRootRoute, Outlet, HeadContent, Scripts } from '@tanstack/react-router'; +import { LazyMotion } from 'framer-motion'; import appCss from './globals.css?url'; +const loadFeatures = () => import('framer-motion').then(res => res.domAnimation); + export const Route = createRootRoute({ head: () => ({ meta: [ @@ -68,7 +71,9 @@ function RootLayout() { Since the Inter Google Font is loaded via the stylesheet above, it will automatically apply if configured in Tailwind CSS. */} - + + + diff --git a/src/app/about.tsx b/src/app/about.tsx index 9551d41..feaf8c9 100644 --- a/src/app/about.tsx +++ b/src/app/about.tsx @@ -1,9 +1,11 @@ import { createFileRoute } from "@tanstack/react-router"; -import { motion } from "framer-motion"; +import { m } from "framer-motion"; import Header from "@/components/Header"; -import Footer from "@/components/Footer"; +// Footer is lazy loaded import { Image } from "@unpic/react"; +import { lazy, Suspense } from "react"; +const Footer = lazy(() => import("@/components/Footer")); export const Route = createFileRoute('/about')({ component: AboutPage, }); @@ -43,14 +45,14 @@ function AboutPage() { {/* Hero Section */}
- - Welcome to the World of Mozimo Magic - + - Indulge in the Luxurious. Immerse in the Captivating. Savor the Fresh. At Mozimo, we create a chocolate experience that transports you to a world of opulence, with mesmerizing aromas and visuals, and delights your taste buds with the vibrant flavors of freshly crafted chocolates. Join us on a journey of pure luxury, captivating moments, and a fresh perspective on the art of chocolate-making.

-
+ {/* Hero Image */} - - @@ -106,9 +108,9 @@ function AboutPage() { className="w-full h-full object-cover img-premium" />
- - - + + +
@@ -118,14 +120,14 @@ function AboutPage() {
{/* Founders Image */} - - @@ -136,18 +138,18 @@ function AboutPage() { className="w-full h-full object-cover img-premium" />
- - + + {/* Founders Text */} - - Our Founders - + - Priyanka and Amritanshu. - + - are the proud founders of Mozimo, a bean-to-bar chocolate shop that celebrates the rich and distinctive flavors of single origin cocoa beans. Their journey with Mozimo has been a passionate pursuit of creating exceptional chocolate that tantalizes the taste buds and tells the unique story of each cocoa bean's origin. - - + +
@@ -204,14 +206,14 @@ function AboutPage() {
{/* Italian Chocolate Text */} - - Capturing the essence of - - + Italian Chocolate - + - With over 15 years of hospitality experience, these avid travelers scoured the globe for the finest cocoa beans and techniques. Deep in remote cocoa farms, they cultivated relationships with farmers committed to sustainability. Carefully selecting beans, they honor each harvest's stories. In their cozy workshop, they roast, crack, and refine beans with modern techniques, capturing their essence in every Mozimo chocolate bar. - - + + {/* Cocoa Farm Image */} - - @@ -279,8 +281,8 @@ function AboutPage() { className="w-full h-full object-cover img-premium" />
- - + +
@@ -289,14 +291,14 @@ function AboutPage() {
{/* Two Images Side by Side */} - -
- + -
- - + + {/* Text Content */} - - Under the mentorship of the master chocolatier Gabriele Rinaudo, Priyanka and Amritanshu dedicated themselves to mastering the intricate art of chocolate making. With unwavering determination and a thirst for knowledge, they immersed themselves in the world of cocoa, learning the nuances of sourcing the finest ingredients and perfecting the delicate techniques that transform raw beans into exquisite chocolate creations. - + - In addition to their training under Gabriele Rinaudo, they embarked on a transformative journey to Italy to further enrich their understanding of the art of chocolate making. This immersive experience in Italy not only broadened their knowledge but also deepened their appreciation for the timeless artistry and dedication that define the world of chocolate making. - - + +
{/* Vision and Passion Section */}
- {/* Founders Outdoor Image */} - @@ -392,10 +394,10 @@ function AboutPage() { className="w-full h-full object-cover img-premium" />
- + {/* Vision and Passion Text */} - Passionate about chocolate as a medium for creativity and exploration, they view it not just as a confection but as a canvas for artistic expression. Constantly seeking inspiration, they push the boundaries of chocolate-making, committed to crafting sensory experiences that delight the taste buds, eyes, and soul. Proud to be part of the global craft chocolate revolution, they aim to inspire others while championing cocoa diversity and sustainability. Back in the workshop, cacao beans are roasted, cracked, and refined with modern techniques, crafting each bar of Mozimo chocolate into a masterpiece, capturing the essence of its origins.

-
- + +
-
+ }> +
+
); } diff --git a/src/app/index.tsx b/src/app/index.tsx index efcb31d..04c5ac8 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -1,14 +1,14 @@ import { createFileRoute } from '@tanstack/react-router'; -import { motion } from "framer-motion"; +import { m } from "framer-motion"; import Header from "@/components/Header"; -import Footer from "@/components/Footer"; +// Footer is lazy loaded import Section from "@/components/Section"; import Button from "@/components/Button"; import BannerSlider from "@/components/BannerSlider"; import { Image } from "@unpic/react"; -import { useState, useEffect, useRef, useCallback } from "react"; -import ProductCategory from "@/components/ProductCategory"; -import SocialMedia from "@/components/SocialMedia"; +import { useState, useEffect, useRef, useCallback, lazy, Suspense } from "react"; +const ProductCategory = lazy(() => import("@/components/ProductCategory")); +const Footer = lazy(() => import("@/components/Footer")); export const Route = createFileRoute('/')({ component: Home, @@ -17,30 +17,35 @@ export const Route = createFileRoute('/')({ // Premium animation variants const fadeInUp = { initial: { opacity: 0, y: 30 }, - animate: { opacity: 1, y: 0 }, - transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }, + animate: { opacity: 1, y: 0, transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] as const } }, }; const fadeInLeft = { initial: { opacity: 0, x: -30 }, - animate: { opacity: 1, x: 0 }, - transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }, + animate: { opacity: 1, x: 0, transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] as const } }, }; const fadeInRight = { initial: { opacity: 0, x: 30 }, - animate: { opacity: 1, x: 0 }, - transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }, + animate: { opacity: 1, x: 0, transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] as const } }, }; const staggerContainer = { + initial: { opacity: 0 }, animate: { + opacity: 1, transition: { staggerChildren: 0.2, + delayChildren: 0.1, }, }, }; +const itemUp = { + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0, transition: { duration: 0.6 } } +}; + const categories = [ "Bars", "Barks", @@ -134,14 +139,14 @@ function Home() {
{/* Brand Story - Left Text */} - - Brand Story - +
- - + We meticulously roast, crack, winnow, and refine beans in-house, using modern techniques to highlight their natural flavors. - - + Each chocolate is a masterpiece, capturing the essence of cocoa in its purest form. - +
- Discover more - -
+ + {/* Brand Story - Right Image */} - - @@ -227,18 +232,18 @@ function Home() { draggable={false} />
- - + + {/* Chocolate Tempering - Left Video */} - - @@ -253,11 +258,11 @@ function Home() { aria-label="Chocolate pouring from metallic spout - Mozimo chocolate tempering process" />
- - + + {/* Chocolate Tempering - Right Text */} -
- Discover the delicate art of our - - + Chocolate Tempering - +
- - - + +
{/* Our Collections Section */}
- Our Collections - + - ( -
@@ -410,18 +414,20 @@ function Home() { {item.title}
-
+ ))} -
+
- + }> + + {/* Social Media Call to Action */} - + {/* */} {/* In the Spotlight Section */}
- In the Spotlight - +
- - + ); })} - +
-
+ }> +
+
); } diff --git a/src/components/BannerSlider.tsx b/src/components/BannerSlider.tsx index 9608213..9430c5b 100644 --- a/src/components/BannerSlider.tsx +++ b/src/components/BannerSlider.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useRef } from "react"; -import { motion, AnimatePresence } from "framer-motion"; +import { m, AnimatePresence } from "framer-motion"; import { Image } from "@unpic/react"; const banners = [ @@ -95,7 +95,7 @@ export default function BannerSlider() { {/* Banner Images with Premium Animations */} - - + {/* Premium Shop Now Button */} {currentIndex === 0 && (
- Shop Now - +
)} @@ -167,7 +167,7 @@ export default function BannerSlider() { {showArrows && ( <> - - + - - + )} {/* Mobile Navigation Arrows */}
- - + - - +
{/* Premium Dots Indicator */}
{banners.map((_, index) => ( - goToSlide(index)} className={`w-2 h-2 md:w-3 md:h-3 rounded-full transition-all duration-300 ${ @@ -289,7 +289,7 @@ export default function BannerSlider() { {/* Premium Progress Bar */}
- {/* Column 1: Brand Identity */} - - + {/* Column 2: Get in touch */} - - 0172-4045414 - - + SCO 8, Inner Market, 9-D, Sector 9, Chandigarh, 160009 - +
- + {/* Column 3: Know more */} - ( - - + ))} - + {/* Column 4: FAQ, Store, Ordering, Social Media */} - {["FAQ", "Locate our store", "Bulk Ordering"].map((link, index) => ( - - + ))}
@@ -225,7 +225,7 @@ export default function DesktopFooter() {
{/* Facebook */} - - + {/* Instagram */} - - + {/* YouTube */} - - + {/* LinkedIn */} - - +
- + {/* Bottom Bar */} - © 2024 Mozimo. All rights reserved.

-
+ ); } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 504cf86..c2475f3 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,4 +1,4 @@ -import { motion, AnimatePresence } from "framer-motion"; +import { m, AnimatePresence, useScroll, useMotionValueEvent } from "framer-motion"; import { Image } from "@unpic/react"; import { useState, useEffect } from "react"; import { useLocation } from "@tanstack/react-router"; @@ -8,15 +8,11 @@ export default function Header() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const pathname = useLocation({ select: (loc) => loc.pathname }); - useEffect(() => { - const handleScroll = () => { - const scrollPosition = window.scrollY; - setIsScrolled(scrollPosition > 100); - }; + const { scrollY } = useScroll(); - window.addEventListener("scroll", handleScroll); - return () => window.removeEventListener("scroll", handleScroll); - }, []); + useMotionValueEvent(scrollY, "change", (latest: number) => { + setIsScrolled(latest > 100); + }); // Close mobile menu when clicking outside useEffect(() => { @@ -59,7 +55,7 @@ export default function Header() { return ( <> {/* Mobile Header - Only visible on mobile */} - {/* Left: Hamburger Menu */}
-
- - -
-
+
{/* Center: Logo */}
- - - - + +
{/* Right: Action Buttons */}
- - + - - + - - +
-
+ {/* Desktop Header - Only visible on desktop */} - {/* Left Navigation */}
- {pathname === "/about" ? "Home" : "About us"} - - + Shop - +
{/* Center Logo */} - - + {/* Right Icons */}
{/* More Dropdown */} - More - - - + + - - - + - - + - +
-
+ {/* Mobile Menu Overlay - Only visible on mobile */} {isMobileMenuOpen && ( - - {/* Mobile Navigation Links */}
- {pathname === "/about" ? "Home" : "About us"} - - + Shop - - + Categories - +
-
-
+ + )}
diff --git a/src/components/MobileFooter.tsx b/src/components/MobileFooter.tsx index 0153543..14aa277 100644 --- a/src/components/MobileFooter.tsx +++ b/src/components/MobileFooter.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { m } from "framer-motion"; import { Image } from "@unpic/react"; export default function MobileFooter() { @@ -11,7 +11,7 @@ export default function MobileFooter() {
{/* Logo at top */} - - + {/* Two column layout */}
{/* Left Column - Contact Info */} - - 0172-4045414 - - + SCO 8, Inner Market, 9-D, Sector 9, Chandigarh, 160009 - +
{/* Find Us On */} @@ -118,7 +118,7 @@ export default function MobileFooter() {
{/* Social icons with original animations */} - - - + - - + - - + - + {/* Other social icons... */}
- + {/* Right Column - Know more and other links */} - ( - - + ))} - + {/* Copyright - original styling preserved */} - © 2024 Mozimo. All rights reserved.

-
+ ); diff --git a/src/components/ProductCategory.tsx b/src/components/ProductCategory.tsx index d954faf..78a444f 100644 --- a/src/components/ProductCategory.tsx +++ b/src/components/ProductCategory.tsx @@ -1,5 +1,5 @@ import { useState, useCallback, useMemo, useEffect } from "react"; -import { LazyMotion, domAnimation, m } from "framer-motion"; +import { m } from "framer-motion"; import Section from "@/components/Section"; import { Image } from "@unpic/react"; @@ -94,7 +94,7 @@ import { Image } from "@unpic/react"; return (
- +
{/* Left */}
@@ -123,7 +123,7 @@ import { Image } from "@unpic/react";
-
+
); } diff --git a/src/components/Section.tsx b/src/components/Section.tsx index 825eab8..f13630d 100644 --- a/src/components/Section.tsx +++ b/src/components/Section.tsx @@ -1,6 +1,6 @@ 'use client'; -import { motion } from 'framer-motion'; +import { m } from 'framer-motion'; import { ReactNode } from 'react'; interface SectionProps { @@ -35,7 +35,7 @@ export default function Section({ return (
- {children} - +
); } diff --git a/src/components/SocialMedia.tsx b/src/components/SocialMedia.tsx index 0ebdb15..ad5a078 100644 --- a/src/components/SocialMedia.tsx +++ b/src/components/SocialMedia.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef } from "react"; -import { motion } from "framer-motion"; +import { m } from "framer-motion"; import { Image } from "@unpic/react"; import Section from "./Section"; import { useInstagram } from "@/hooks/useInstagram"; @@ -60,7 +60,7 @@ export default function SocialMedia() { return (
- Follow us on Instagram - +
{[1, 2, 3].map((i) => ( - - In the meantime, follow us @mozimo_chocolate
- + ) : ( <> {/* Left Block - Chocolate Spread Jar */} -
-
-
+ - + {/* Middle Block - Magazine Article */} - - + {/* Right Block - World Chocolate Day */} - - + ) ) : ( posts?.slice(0, 3).map((post, index) => ( - - + )) )} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json index c133409..b1dbde6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,15 +13,10 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, - "plugins": [ - { - "name": "next" - } - ], "paths": { "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] }