Skip to content

Conversation

@vivekyadav-3
Copy link

Implemented professional skeleton loaders to improve the user experience on slow networks (3G/4G).

Key changes:

  • Created a reusable Skeleton primitive in components/ui/skeleton.tsx.
  • Implemented BlogPageSkeleton and PostPageSkeleton to match existing layouts.
  • Integrated skeleton loaders in _app.tsx for smooth route transitions.
  • Replaced the basic spinner with a more modern, layout-aware loading state.

Fixes #3293 in the main keploy repository.

Copilot AI review requested due to automatic review settings December 25, 2025 07:33
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces a basic Lottie animation spinner with professional skeleton loaders that match the actual page layouts, improving the user experience during route transitions on slow networks.

  • Introduces a reusable Skeleton primitive component with Tailwind-based pulse animation
  • Implements layout-aware skeleton components for blog listing and individual post pages
  • Updates the main app to conditionally render appropriate skeletons based on the target route

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pages/_app.tsx Replaces PageLoader with skeleton components; adds route-based skeleton selection logic and framer-motion animations for smooth transitions
components/ui/skeleton.tsx Introduces base Skeleton component with pulse animation and customizable styling
components/skeletons/BlogPageSkeleton.tsx Creates skeleton loader matching blog listing page layout with header, hero section, and post grid
components/skeletons/PostPageSkeleton.tsx Creates skeleton loader matching individual post page layout with header, author info, and content blocks
components/skeletons/PostCardSkeleton.tsx Creates skeleton loader for individual post cards used in the blog grid

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import { AnimatePresence } from "framer-motion";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useState } from "react";
import dynamic from 'next/dynamic'
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic import from 'next/dynamic' is no longer used and should be removed since PageLoader has been replaced with skeleton loaders.

Suggested change
import dynamic from 'next/dynamic'

Copilot uses AI. Check for mistakes.
pages/_app.tsx Outdated
Comment on lines 40 to 45
if (targetPath.includes('/technology/') || targetPath.includes('/community/')) {
if (targetPath === '/technology' || targetPath === '/community' || targetPath === '/technology/' || targetPath === '/community/') {
return <BlogPageSkeleton />;
}
return <PostPageSkeleton />;
}
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for determining whether to show BlogPageSkeleton has a flaw. When navigating to '/technology/' or '/community/', the condition targetPath.includes('/technology/') will match, but then the exact path comparison checks for '/technology/' which will also match, returning BlogPageSkeleton. However, for paths like '/technology/my-post', the includes check will match but the exact comparison will fail, returning PostPageSkeleton as intended. This logic works correctly but could be simplified and made more maintainable by checking if the path ends with the section name first, then checking if it includes the section. Consider refactoring to: check if path starts with '/technology' or '/community', then check if it's exactly that path (with or without trailing slash) to return BlogPageSkeleton, otherwise return PostPageSkeleton.

Suggested change
if (targetPath.includes('/technology/') || targetPath.includes('/community/')) {
if (targetPath === '/technology' || targetPath === '/community' || targetPath === '/technology/' || targetPath === '/community/') {
return <BlogPageSkeleton />;
}
return <PostPageSkeleton />;
}
const startsWithTechnology = targetPath.startsWith('/technology');
const startsWithCommunity = targetPath.startsWith('/community');
if (startsWithTechnology || startsWithCommunity) {
const normalizedPath =
targetPath.length > 1 && targetPath.endsWith('/')
? targetPath.slice(0, -1)
: targetPath;
if (normalizedPath === '/technology' || normalizedPath === '/community') {
return <BlogPageSkeleton />;
}
return <PostPageSkeleton />;
}

Copilot uses AI. Check for mistakes.
pages/_app.tsx Outdated
Comment on lines 40 to 44
if (targetPath.includes('/technology/') || targetPath.includes('/community/')) {
if (targetPath === '/technology' || targetPath === '/community' || targetPath === '/technology/' || targetPath === '/community/') {
return <BlogPageSkeleton />;
}
return <PostPageSkeleton />;
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The skeleton selection logic doesn't account for the '/community/search' route. When navigating to '/community/search', the condition targetPath.includes('/community/') will be true, and since it's not exactly '/community' or '/community/', it will show PostPageSkeleton instead of a more appropriate skeleton for a search page. Consider adding specific handling for search routes or using BlogPageSkeleton for search pages.

Suggested change
if (targetPath.includes('/technology/') || targetPath.includes('/community/')) {
if (targetPath === '/technology' || targetPath === '/community' || targetPath === '/technology/' || targetPath === '/community/') {
return <BlogPageSkeleton />;
}
return <PostPageSkeleton />;
// Use blog-style skeleton for community search routes
if (targetPath.startsWith('/community/search')) {
return <BlogPageSkeleton />;
}
if (targetPath.includes('/technology/') || targetPath.includes('/community/')) {
if (
targetPath === '/technology' ||
targetPath === '/community' ||
targetPath === '/technology/' ||
targetPath === '/community/'
) {
return <BlogPageSkeleton />;
}
return <PostPageSkeleton />;

Copilot uses AI. Check for mistakes.
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted bg-gray-200 dark:bg-gray-800", className)}
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class string contains redundant background color definitions. The string includes both bg-muted and explicit bg-gray-200 dark:bg-gray-800. If bg-muted is defined in your Tailwind theme to provide background colors, the explicit bg-gray-200 dark:bg-gray-800 may override it or create conflicts. Consider using only one approach - either use the semantic bg-muted token or the explicit color classes, not both.

Suggested change
className={cn("animate-pulse rounded-md bg-muted bg-gray-200 dark:bg-gray-800", className)}
className={cn("animate-pulse rounded-md bg-gray-200 dark:bg-gray-800", className)}

Copilot uses AI. Check for mistakes.

export default function BlogPageSkeleton() {
return (
<div className="min-h-screen bg-white">
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The skeleton components lack ARIA attributes for accessibility. Screen readers should be informed that content is loading. Consider adding role="status" and aria-live="polite" to the root div, along with a visually hidden loading message using aria-label="Loading content" or a screen reader-only text element.

Suggested change
<div className="min-h-screen bg-white">
<div
className="min-h-screen bg-white"
role="status"
aria-live="polite"
>
<span className="sr-only">Loading blog content</span>

Copilot uses AI. Check for mistakes.

export default function PostPageSkeleton() {
return (
<div className="min-h-screen bg-white">
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The skeleton components lack ARIA attributes for accessibility. Screen readers should be informed that content is loading. Consider adding role="status" and aria-live="polite" to the root div, along with a visually hidden loading message using aria-label="Loading content" or a screen reader-only text element.

Suggested change
<div className="min-h-screen bg-white">
<div
className="min-h-screen bg-white"
role="status"
aria-live="polite"
aria-label="Loading content"
>
<span className="sr-only">Loading content</span>

Copilot uses AI. Check for mistakes.

export default function PostCardSkeleton() {
return (
<div className="bg-white rounded-xl shadow-[0_6px_18px_rgba(0,0,0,0.08)] border border-gray-200 overflow-hidden h-full">
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The skeleton components lack ARIA attributes for accessibility. Screen readers should be informed that content is loading. Consider adding role="status" and aria-live="polite" to the root div, along with a visually hidden loading message using aria-label="Loading content" or a screen reader-only text element.

Suggested change
<div className="bg-white rounded-xl shadow-[0_6px_18px_rgba(0,0,0,0.08)] border border-gray-200 overflow-hidden h-full">
<div
className="bg-white rounded-xl shadow-[0_6px_18px_rgba(0,0,0,0.08)] border border-gray-200 overflow-hidden h-full"
role="status"
aria-live="polite"
aria-label="Loading content"
>

Copilot uses AI. Check for mistakes.
- Convert author-description.jsx to TypeScript with proper interfaces
- Convert waitlistBanner.jsx to TypeScript with React.FC typing
- Add asChild prop to Header Sign In buttons for better semantics
- Fix placeholder Home URL in footer (changed from #0 to https://keploy.io/)
- Add proper TypeScript types and null checks in author-description
- Improve accessibility by avoiding nested interactive elements
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants