react-loader and other dependencies are included in the client bundle
Opened this issue · 2 comments
Describe the bug
While migrating to Presentation/Loaders, I noticed that the client side bundle size dramatically increased. Analyzing the bundle on a fresh clone of this repo shows that react-loader and related dependencies are included in the client bundle, for example:
async-cache-dedupe
@sanity/react-loader
safe-stable-stringify
@sanity/client/*
@portabletext/react
(since the production site should be entirely server side rendered)CustomPortableText.tsx
(same as above)HomePagePreview.tsx
queries.ts
@sanity/overlays
stega.browser.js
To Reproduce
Steps to reproduce the behavior:
- Clone this repository
- Install @next/bundle-analyzer as per its readme
- Run
ANALYZE=true npm run build
- Look at the client.html file
- Also notice the output from next build which shows a 121kb "First Load JS" for the homepage route, higher than expected.
Expected behavior
react-loader
, @sanity/client
, @sanity/overlays
and other dependencies related to live previews and visual editing are expected to be lazy loaded only when draft mode is enabled, thus not affecting the size of the bundle shipped to website visitors.
Screenshots
This is app/(personal)/page
:
This is app/(personal)/layout
:
Which versions of Sanity are you using?
@sanity/cli (global) 3.20.2 (latest: 3.23.4)
@sanity/demo 1.0.2 (up to date)
@sanity/image-url 1.0.2 (up to date)
@sanity/overlays 2.2.0 (latest: 2.3.0)
@sanity/preview-url-secret 1.3.4 (latest: 1.4.0)
@sanity/react-loader 1.6.1 (latest: 1.6.4)
@sanity/vision 3.21.3 (latest: 3.23.4)
sanity 3.21.3 (latest: 3.23.4)
What operating system are you using?
MacOS Sonoma 14.1.2
Which versions of Node.js / npm are you running?
10.2.4
v18.19.0
Additional context
I know you're actively working on this starter and the API:s in general so this may well be something you are aware of and working on, but I thought I'd open this in case it is of any help. 😊 To my eye, everything in this starter looks good, i.e it uses next/dynamic
throughout, so I'm really not sure why so much would be included in the bundle. Could this be a Next.js issue?
Hi! When we tested this way back it wasn't part of the bundle, could be a regression?
In any case we have reproduced the problem and different patterns (using React.lazy
directly instead of next/dynamic
etc). The docs suggest Next should be able to infer that components that are lazy loaded in draft mode doesn't need to be in the production bundle.
Could you open an issue on Vercel's end?
I don't think the repro needs to be more complicated than:
// app/layout.tsx
import dynamic from 'next/dynamic'
import { draftMode } from 'next/headers'
const HeavyDraftModeComponent = dynamic(() => import('./HeavyDraftModeComponent'))
export default function Layout() {
if(draftMode().isEnabled) {
return <HeavyDraftModeComponent />
}
return 'Production Mode'
}
// app/HeavyDraftModeComponent.tsx
'use client'
// In Real World apps it imports heavy client libraries that are only used client side in Draft Mode, and server-only in production
console.log('Should only log in Draft Mode')
export default function HeavyDraftModeComponent() {
return 'Draft Mode'
}
I ran into a similar issue with finding my own components (meant for prerender only) in the client bundle, to fix I used dynamic
to import the heavy components inside the preview component
https://github.com/sanity-io/template-nextjs-personal-website/blob/main/components/pages/home/HomePagePreview.tsx#L9
e.g.
// HomePagePreview.tsx
'use client'
import dynamic from "next/dynamic";
import { type QueryResponseInitial } from '@sanity/react-loader'
import { homePageQuery } from '@/sanity/lib/queries'
import { useQuery } from '@/sanity/loader/useQuery'
import { HomePagePayload } from '@/types'
// import HomePage from './HomePage'
const HomePage = dynamic(() => import("./HomePage"))
type Props = {
initial: QueryResponseInitial<HomePagePayload | null>
}
export default function HomePagePreview(props: Props) {
const { initial } = props
const { data, encodeDataAttribute } = useQuery<HomePagePayload | null>(
homePageQuery,
{},
{ initial },
)
if (!data) {
return (
<div className="text-center">
Please start editing your Home document to see the preview!
</div>
)
}
return <HomePage data={data} encodeDataAttribute={encodeDataAttribute} />
}
This solves my issue of 'react-icons' (a massive library) getting leaked onto the client. As for the other sanity packages, I'm not sure yet, but I'll report back if I find a solution.
Cheers
EDIT: I've been trying to figure out how to prevent my sanity queries (along with zod and groqd) from getting into the client but haven't had any luck, probably tried about 12 different variations of dynamic and lazy but they always seem to leak. Thankfully the queries and types aren't that big, but it would be nice to not have them on the frontend. So if anyone knows a way please share, thanks!