template-nextjs-personal-website icon indicating copy to clipboard operation
template-nextjs-personal-website copied to clipboard

react-loader and other dependencies are included in the client bundle

Open marcusforsberg opened this issue 6 months ago • 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:

  1. Clone this repository
  2. Install @next/bundle-analyzer as per its readme
  3. Run ANALYZE=true npm run build
  4. Look at the client.html file
  5. 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:

Screenshot 2024-01-02 at 08 19 49

This is app/(personal)/layout:

Screenshot 2024-01-02 at 08 20 46

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?

marcusforsberg avatar Jan 02 '24 07:01 marcusforsberg

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'
}

stipsan avatar Jan 02 '24 18:01 stipsan

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!

image

JohnGemstone avatar Jan 15 '24 23:01 JohnGemstone