Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/heavy-steaks-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-app": patch
---

If a page load takes longer than 300 ms, log the total duration to Sentry to identify potentially slow pages.
5 changes: 5 additions & 0 deletions .changeset/shy-times-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-app": minor
---

If a page load takes longer than 300 ms, show a loading spinner and blur the page to prevent double-clicking the navigation action.
5 changes: 5 additions & 0 deletions .changeset/wide-animals-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-app": patch
---

Suppress logging `BodyStreamBuffer was aborted` to Sentry, as this is caused by users navigating to another page while the current page is still loading.
83 changes: 83 additions & 0 deletions fdm-app/app/components/custom/navigation-progress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as Sentry from "@sentry/react-router"
import { AnimatePresence, motion } from "framer-motion"
import { Loader2 } from "lucide-react"
import { useEffect, useRef, useState } from "react"
import { useNavigation } from "react-router"
import { clientConfig } from "~/lib/config"

/**
* Shows a blurred overlay with a loading card when navigation takes longer than 300ms.
* Fast navigations never trigger the indicator.
* Tracks show frequency and duration as Sentry metrics.
*/
export function NavigationProgress() {
const { state } = useNavigation()
const [show, setShow] = useState(false)
const startTimeRef = useRef<number | null>(null)

// Show after 300ms — emit a count metric when it appears
useEffect(() => {
if (state !== "idle") {
if (startTimeRef.current === null) {
startTimeRef.current = Date.now()
}
const timer = setTimeout(() => {
setShow(true)
if (clientConfig.analytics.sentry) {
Sentry.metrics.count("navigation_progress.shown", 1)
}
}, 300)
return () => clearTimeout(timer)
}

// Navigation finished — emit duration metric and hide
if (show && startTimeRef.current !== null) {
const duration = Date.now() - startTimeRef.current
if (clientConfig.analytics.sentry) {
Sentry.metrics.distribution(
"navigation_progress.duration_ms",
duration,
)
}
}
setShow(false)
startTimeRef.current = null
}, [state, show])

return (
<AnimatePresence>
{show && (
<>
{/* Backdrop blur */}
<motion.div
key="nav-backdrop"
className="fixed inset-0 z-9998 bg-background/50 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.18 }}
/>

{/* Loading card */}
<motion.div
key="nav-card"
className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-9999 flex flex-col items-center gap-3 rounded-xl border border-border bg-background px-8 py-6 shadow-xl"
initial={{ opacity: 0, scale: 0.92, y: "-48%" }}
animate={{ opacity: 1, scale: 1, y: "-50%" }}
exit={{ opacity: 0, scale: 0.96, y: "-48%" }}
transition={{
type: "spring",
stiffness: 420,
damping: 28,
}}
>
<Loader2 className="h-6 w-6 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">
Even geduld…
</p>
</motion.div>
</>
)}
</AnimatePresence>
)
}
1 change: 1 addition & 0 deletions fdm-app/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ if (clientConfig.analytics.sentry) {
dsn: sentryConfig.dsn,
release: import.meta.env.PUBLIC_APP_VERSION,
environment: import.meta.env.NODE_ENV,
ignoreErrors: [/BodyStreamBuffer was aborted/],
integrations: [
Sentry.reactRouterTracingIntegration(),
Sentry.replayIntegration(),
Expand Down
2 changes: 2 additions & 0 deletions fdm-app/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getToast } from "remix-toast"
import { toast as notify } from "sonner"
import { Banner } from "~/components/custom/banner"
import { ErrorBlock } from "~/components/custom/error"
import { NavigationProgress } from "~/components/custom/navigation-progress"
import { Toaster } from "~/components/ui/sonner"
import { clientConfig } from "~/lib/config"
import { useChangelogStore } from "~/store/changelog"
Expand Down Expand Up @@ -152,6 +153,7 @@ export function Layout() {
</head>
<body>
<Outlet />
<NavigationProgress />
<Banner />
<Toaster />
<ErrorBoundary error={null} params={{}} />
Expand Down
1 change: 1 addition & 0 deletions fdm-app/instrument.server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Sentry.init({
integrations: [nodeProfilingIntegration()],
tracesSampleRate: Number(process.env.VITE_SENTRY_TRACE_SAMPLE_RATE),
profilesSampleRate: Number(process.env.VITE_SENTRY_PROFILE_SAMPLE_RATE),
ignoreErrors: [/BodyStreamBuffer was aborted/],
environment: process.env.NODE_ENV ?? "development",
release: process.env.npm_package_version,
})