Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prebuild JS Bundles for CDN #3284

Closed
chrisui opened this issue Nov 15, 2017 · 15 comments
Closed

Prebuild JS Bundles for CDN #3284

chrisui opened this issue Nov 15, 2017 · 15 comments

Comments

@chrisui
Copy link

chrisui commented Nov 15, 2017

Would it be possible for next build to prebuild all js bundles from pages and spit them out so they can be uploaded to the cdn? I can see next export however I still want the server for the initial render but also want to leverage a CDN for my js assets.

@sergiodxa
Copy link
Contributor

That's exactly what next build does, you can see all the JS files inside .next directory and there you can find the client side code required.

That's also why Next.js custom config support assetPrefix.

@chrisui
Copy link
Author

chrisui commented Nov 15, 2017

Ah gotcha. I've got some other bad config emitting a build and dist folder which confused me.

Thanks for clarification!

@pcardune
Copy link

pcardune commented Dec 9, 2017

Not sure what I'm doing wrong, but this doesn't seem to be working for me. The browser is requesting files from urls like

/_next/9cb53b14-3000-42e4-8b61-7e87ee82ea4a/page/podcast-episode

But these paths do not show up in the output of next build. Here is what I get in my .next directory (which I've renamed to build):

build
build/dist
build/dist/ui
build/dist/ui/BlogPostCard.js
build/dist/ui/Section.js
build/dist/ui/ProfileCard.js
build/dist/ui/PageLayout.js
build/dist/ui/GlobalNav.js
build/dist/ui/Colors.js
build/dist/ui/Hero.js
build/dist/ui/ProfileGrid.js
build/dist/ui/PodcastEpisodeCard.js
build/dist/ui/TrustedHTML.js
build/dist/ui/PodcastPlayer.js
build/dist/ui/Footer.js
build/dist/CMS.js
build/dist/styles
build/dist/styles/getContext.js
build/dist/components
build/dist/components/withRoot.js
build/dist/propTypes.js
build/dist/pages
build/dist/pages/blog-index.js
build/dist/pages/_error.js
build/dist/pages/login.js
build/dist/pages/home-page.js
build/dist/pages/podcast-index.js
build/dist/pages/blog-post.js
build/dist/pages/_document.js
build/dist/pages/podcast-episode.js
build/manifest.js
build/BUILD_ID
build/main.js
build/build-stats.json
build/commons.js
build/app.js
build/bundles
build/bundles/pages
build/bundles/pages/blog-index.js
build/bundles/pages/_error.js
build/bundles/pages/login.js
build/bundles/pages/home-page.js
build/bundles/pages/podcast-index.js
build/bundles/pages/blog-post.js
build/bundles/pages/_document.js
build/bundles/pages/podcast-episode.js

So what am I supposed to be uploading to the CDN?

@arunoda
Copy link
Contributor

arunoda commented Dec 10, 2017

@pcardune You don't need to upload anything.
Most of the CDN server offers custom origin support

  • Basically, you set a custom origin to yourapp.com and get a CDN url like abc.mycdn.com
  • Then you default a new version of your app and set it to yourapp.com with the abc.mycdn.com as the assetPrefix

So,

  • When the browser asks abc.mycdn.com/_next/*** for the first time, CDN will fetch it from the yourapp.com and cache it inside it.

This is the same thing we do for https://zeit.co.

In our case, assetPrefix is https://assets.zeit.co/raw/upload/front-assets

@pcardune
Copy link

Yes, I'm familiar with using a CDN as a proxy to your application servers.

But I think it is still useful to be able to get all the assets in static file form for the following reasons:

  1. Warming CDN caches before deploying new versions (https://www.cdnplanet.com/guides/prefetch/)

  2. Using many of the "turn-key" CDN solutions that are backed by cloud file storage (S3, Google Cloud Storage, etc) where the simplest configuration is to just point the CDN to a storage bucket.

@unregistered
Copy link
Contributor

We're currently looking into uploading assets to a bucket as well, it seems like we'll need to generate the mappings ourselves.

It doesn't seem like using the application servers as the origin would work though because of the versioning requirements, especially if the application servers are behind a load balancer, and rolling deploys means that multiple versions can coexist.

@dav-is
Copy link
Contributor

dav-is commented Jan 28, 2018

Plus one. Can we get this issue reopened?

@sergiodxa
Copy link
Contributor

Next.js detects a BUILD_ID change and reload the page to use the new built files.

So if an user access to a Next.js app and start navigating the page, then the developer build a new version, the internal BUILD_ID will change, if the user try to navigate to another page client side Next.js will detect the mismatch of the BUILD_ID and do a page reload, then it will download the new built files, so you don't need to have two versiones coexisting, user of the old version will be forced to load the new bundles.

@dav-is
Copy link
Contributor

dav-is commented Jan 28, 2018

Google cloud cdn doesn’t support remote origin and uses google storage. Is it too much to ask that next outputs truly static bundles. Seems a bit needless that static file requests run through nodejs to begin with.

@jsnick
Copy link

jsnick commented Jan 30, 2018

@sergiodxa
Suppose there are over two instances behind a load balancer, which are next.js app.
and CDN origin is web server. (Endpoint is LB)

If I deploy a new version, rolling update is started.
Then the old and new versions could be coexisting.

At that time, a user requested my app, and the LB send the request to a new version.
It'll receive the new version which endpoint of the new assets is included.
Then it'll send the request of new assets to CDN.
CDN has no cache about it, so cdn request it to origin. (LB)

But this time, If the LB send the reqeust of CDN to the old version instance, CDN will receive a response 404. because the old one doesn't have new assets.
CDN could cache the endpoint of new assets to 404.

So, I will build my next.js app, make BUILD_ID directory in my origin (like S3) and upload files in dist to BUILD_ID directory.

@dav-is
Copy link
Contributor

dav-is commented Jan 30, 2018

Exactly and would be the case for some forms of AB testing where you deploy two versions to different groups of users

@unregistered
Copy link
Contributor

@jsnick good summary. We’ve already implemented exactly that at my workplace. It works well so far. Not sure if there’s interest for an open source contribution

@gcpantazis
Copy link
Contributor

Just to follow up on this, we were able to get this working with rolling deploys on S3 (ECS with CloudFront sitting in front of the services and S3). All we really need "custom" on the Next side is assetPrefix.

Dockerfile:

FROM mhart/alpine-node:8

ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG DEPLOYMENT_TAG

# This preserves the argument into the docker image for subsequent deployments.
ENV DEPLOYMENT_TAG=${DEPLOYMENT_TAG}

RUN apk --no-cache update && \
    apk --no-cache add python py-pip py-setuptools ca-certificates curl groff less && \
    pip --no-cache-dir install awscli && \
    rm -rf /var/cache/apk/*

WORKDIR /app
COPY package.json package-lock.json /app/
RUN npm install

COPY . /app

RUN npm run build && \
    # Build the next static bundle
    npm run export && \
    # You don't need the static files for the hashed CDN deployment
    rm -rf ./out/static && \
    # We use S3, which sits behind CloudFront. $DEPLOYMENT_TAG is an env variable
    # from Bitbucket Pipelines which in our case corresponds to the git hash for the deployment.
    aws s3 sync ./out s3://frontend-builds.website.com/_builds/$DEPLOYMENT_TAG \
        --cache-control public,max-age=31464488 && \
    # Don't need the export now that it's synced to S3.
    rm -rf ./out

CMD npm run start

EXPOSE 3000

next.config.js:

module.exports = {
  assetPrefix: process.env.DEPLOYMENT_TAG ? `/_builds/${process.env.DEPLOYMENT_TAG}` : '',
};

You need to set a behavior on your CloudFront distribution pointing _build/* to the S3 bucket, in this example.

@vaibhavi3t
Copy link

@sergiodxa

I set assetPrefix in next.config.js like below:

module.exports = {
  assetPrefix: isProd ? 'https://dw745fgl22f1q.cloudfront.net/pwa-ssr/' : ''
};

It's added following script

<script async="" id="__NEXT_PAGE__/home" src="https://dw745fgl22f1q.cloudfront.net/pwa-ssr/_next/4c667dfa-d920-4973-9214-dec4485990b1/page/home.js"></script>
<script async="" id="__NEXT_PAGE__/_app" src="https://dw745fgl22f1q.cloudfront.net/pwa-ssr/_next/4c667dfa-d920-4973-9214-dec4485990b1/page/_app.js"></script>
<script async="" id="__NEXT_PAGE__/_error" src="https://dw745fgl22f1q.cloudfront.net/pwa-ssr/_next/4c667dfa-d920-4973-9214-dec4485990b1/page/_error.js"></script>
<script src="https://dw745fgl22f1q.cloudfront.net/pwa-ssr/_next/static/commons/main-b982386b18bef2694dbd.js" async=""></script>

I uploaded .next folder to my CDN

I'm able to get main.js bundle with 200 OK but getting 400 Error for other pages. You can see there is build number in the path but there is not such path in the .next folder. so it's not able to resolve.
How can I fix it?

@Janpot
Copy link
Contributor

Janpot commented Dec 7, 2018

The .next folder seems to contain server code as well. Is it safe to upload that to a CDN?

Is it possible to just configure assetpath, build, and host the .next/static under that same path?

@lock lock bot locked as resolved and limited conversation to collaborators Dec 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants