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

feat: split up API Routes + use .nft.json files to make builds fast #2058

Merged
merged 75 commits into from
May 12, 2023

Conversation

Skn0tt
Copy link
Contributor

@Skn0tt Skn0tt commented Apr 20, 2023

Summary

Brings back split API Routes, but with faster bundling.

Splitting up API Routes into own Lambdas can result in reduced bundle size, which we were hoping for when it was first implemented. We had to revert that in
#1731, since it increased the bundling times by a lot.

ZISI added a none bundling strategy in netlify/zip-it-and-ship-it#1173. This allows us to skip tracing, which is very expensive. Luckily, Next.js already emits the .nft.json files that should contain all the tracing info we need!
This PR will utilise these two, to bring us split-up API routes with fast bundling.

Test plan

I haven't done a lot of work in this repo before, but i'm hoping that our e2e tests cover all my changes! Ideally, there won't be any noticeable changes in behaviour.

I'm planning to do a staged rollout of this bundling strategy. Once we're happy with how it's working, we can see if we can apply the same to SSR routes.

Relevant links (GitHub issues, Notion docs, etc.) or a picture of cute animal

https://github.com/netlify/pod-compute/issues/252

Standard checks:

  • Check the Deploy Preview's Demo site for your PR's functionality
  • Add docs when necessary

🧪 Once merged, make sure to update the version if needed and that it was published correctly.

Brings back the functionality that was reverted in
#1731, but under a flag.
This will be utterly slow in building,
so let's try to speed that up in the next step!
@Skn0tt Skn0tt requested a review from a team April 20, 2023 13:32
@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for netlify-plugin-nextjs-export-demo ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/netlify-plugin-nextjs-export-demo/deploys/645e7f25a1884a0008e3643a
😎 Deploy Preview https://deploy-preview-2058--netlify-plugin-nextjs-export-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for next-plugin-edge-middleware ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/next-plugin-edge-middleware/deploys/645e7f25389fce00080132ab
😎 Deploy Preview https://deploy-preview-2058--next-plugin-edge-middleware.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for netlify-plugin-nextjs-static-root-demo ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/netlify-plugin-nextjs-static-root-demo/deploys/645e7f25b4de8a0008009790
😎 Deploy Preview https://deploy-preview-2058--netlify-plugin-nextjs-static-root-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for netlify-plugin-nextjs-nx-monorepo-demo ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/netlify-plugin-nextjs-nx-monorepo-demo/deploys/645e7f24f73c530008674e45
😎 Deploy Preview https://deploy-preview-2058--netlify-plugin-nextjs-nx-monorepo-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for next-i18next-demo ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/next-i18next-demo/deploys/645e7f25cf7e2f0008737d3b
😎 Deploy Preview https://deploy-preview-2058--next-i18next-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for netlify-plugin-nextjs-next-auth-demo ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/netlify-plugin-nextjs-next-auth-demo/deploys/645e7f25bd56d900081ba7d3
😎 Deploy Preview https://deploy-preview-2058--netlify-plugin-nextjs-next-auth-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for next-plugin-canary ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/next-plugin-canary/deploys/645e7f25e8d6c700085005a2
😎 Deploy Preview https://deploy-preview-2058--next-plugin-canary.netlify.app/
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for nextjs-plugin-custom-routes-demo ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/nextjs-plugin-custom-routes-demo/deploys/645e7f2556d1eb000871ac96
😎 Deploy Preview https://deploy-preview-2058--nextjs-plugin-custom-routes-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Apr 20, 2023

Deploy Preview for netlify-plugin-nextjs-demo ready!

Name Link
🔨 Latest commit fdcf8d5
🔍 Latest deploy log https://app.netlify.com/sites/netlify-plugin-nextjs-demo/deploys/645e7f252342fb0008f572d3
😎 Deploy Preview https://deploy-preview-2058--netlify-plugin-nextjs-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@github-actions github-actions bot added the type: feature code contributing to the implementation of a feature and/or user facing functionality label Apr 20, 2023
@Skn0tt Skn0tt self-assigned this Apr 20, 2023
@Skn0tt Skn0tt marked this pull request as draft April 20, 2023 13:33
@ascorbic
Copy link
Contributor

Is there a plan to compare build times?

@Skn0tt
Copy link
Contributor Author

Skn0tt commented Apr 20, 2023

Ideally, i'd like to do a phased rollout where I enable this for a portion of Starter accounts, and compare build times, yes. Would be great if you could point me towards how we do phased rollouts with the Next runtime :)

@Skn0tt
Copy link
Contributor Author

Skn0tt commented Apr 20, 2023

I feel like i'm making good progress! I'm successfully deploying individual functions, and it's leveraging the *.nft.json files so we don't need to trace everything. The next blocker is that we don't have a ready *.nft.json file for the next/dist/server, so we need to be a bit clever around which files we include there. I'm thinking we might be able to use NFT for this - the results of that tracing will be usable across all API routes, so it shouldn't cost too much time. Gonna look at that tomorrow!

@ascorbic
Copy link
Contributor

It's ok, the runtime always uses a phased rollout. What I meant though was testing it yourself by generating several API routes and checking how the build time compares.

@Skn0tt Skn0tt marked this pull request as ready for review April 21, 2023 13:14
@Skn0tt
Copy link
Contributor Author

Skn0tt commented Apr 21, 2023

Alright, time for a first check-in! I've been testing with demos/default, and it now generates one Lambda per API Route:

Screenshot 2023-04-21 at 15 15 29

https://64428a02aaf0226f07e85492--nextjs-split-routes.netlify.app/ is a working deployment with this. The files-to-be-included are identified using the .nft.json files. This doesn't cover the next/dist/server files, so i'm using NFT to trace that once. It takes a moment, but only happens once per deploy - so hopefully it's fine.

There's a couple of things that I'd like to address before we can move forward with this:

  1. Pack individual handlers together: The 11mb ZIPs you can see in the screenshot weigh 42mb after extracting, 41.8mb of which lives in node_modules and is duplicated across all zips. Packing them together should be a lot more space efficient for small functions. I'm not sure what the ideal target size is - maybe we can start with 50mb and then iterate on that threshold.
  2. Remove unneeded files from node_modules: All handlers contain node_modules/react-dom/cjs/react-dom.development.js, a 1MB file. I'm pretty certain it'll never be used in production, and there are probably other .development files or sourcemaps that we can exclude.
  3. Measure build time impact: As Matt noted, we need to do some measurements on wether this increases or lowers our bundling times. We're using none, which should skip the expensive tracing in ZISI - but now that we've started to trace inside here, i'm unsure if that only moved the problem. Let's measure it!
  4. Figure out a rollout strategy: How can we roll this out to a portion of Starter accounts, and measure e.g. buildtimes on their real-world data?

Before I address all of that, a rough review would be good though to know if i'm missing something crucial. @orinokai could you take a rough look at this PR and check the approach? It looks fine to me, but I'm missing a bunch of context on this. A dime for your review :D

packages/runtime/src/helpers/flags.ts Outdated Show resolved Hide resolved
packages/runtime/src/helpers/flags.ts Outdated Show resolved Hide resolved
const traceNextServer = async (publish: string): Promise<string[]> => {
const nextServerDeps = await getDependenciesOfFile(join(publish, 'next-server.js'))

// during testing, i've seen `next-server` contain only one line.

Choose a reason for hiding this comment

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

This feels like a weird check?

What if Next.js itself changes the file and the .nft file changes correctly and this arbitrary threshold is triggered? What is the goal of this check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've explained that here: #2058 (comment)

Rephrasing, to add more context: I've seen instances locally where the .nft file contained less than 10 lines, but those were definitely invalid ones, and they resulted in broken bundles. I don't see a case where the .nft file correctly contains less than 10 lines. I'm unsure if the instances i've seen locally are due to not-so-common setup where the plugin is pulled in with npm link. If this happened in prod, it'd result in deployments that break at runtime. To prevent this, i'm adding this build-time check. I'll be closely monitoring it during the rollout. Once I'm certain this doesn't occur in production, I'll remove the check.

throw new Error("next-server.js.nft.json didn't contain all dependencies.")
}

const filtered = nextServerDeps.filter((f) => {

Choose a reason for hiding this comment

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

This filter list could be its own array of strings in a const and then we could add more stuff if necessary more easily?

Copy link
Contributor Author

@Skn0tt Skn0tt May 11, 2023

Choose a reason for hiding this comment

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

I could imagine that in the future, there's different kinds of patterns than .endsWith that we want to match against, so having this as a filter function is the most versatile. Having said that, I'm fine with replacing this with an array of strings for now, if you prefer that.

Comment on lines 40 to 49
if (headers['x-prerender-revalidate'] && this.netlifyConfig.revalidateToken) {
// handle on-demand revalidation by purging the ODB cache
await this.netlifyRevalidate(url)

res = res as unknown as BaseNextResponse
res.statusCode = 200
res.setHeader('x-nextjs-cache', 'REVALIDATED')
res.send()
} else {
await handler(req, res, parsedUrl)
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels dangerous to me - this change apply to everyone and not just folks opted into feature flag.

I don't have a lot of context on inner workings of refresh hooks (that's @orinokai expertise), so it might be fine not to exec handler but maybe we should consider using this method only when we are splitting api routes to limit potential blast radius?

I assume that this stuff is related to #2058 (comment) and calling it result in MODULE_NOT_FOUND errors you mentioned in earlier comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Your assumption is right. I agree this feels dangerous - i'll see if there's an easy solution to subjecting this to the featureflag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

put it behind the flag in 30db86a.

Copy link
Contributor

Choose a reason for hiding this comment

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

When originally implementing this feature, i wasn't sure whether it was necessary to execute the original Next.js handler - it's not needed for the Netlify revalidate to work - but I left it in because it allows Next.js to validate the input and potentially throw an error, which will block the revalidate from happening. However, it was just a precaution (I can't think of a scenario where this would be necessary because our own revalidate API can also handle these errors) so i think it's fine to go with your rework. In fact, I don't think you need to put it behind the feature flag because the bulk of the change is already behind the this.netlifyConfig.revalidateToken condition, which itself is feature-flagged. You also don't need to await the original handler with that method, you can just return it (which is the how the default Next.js handler works).

Copy link
Contributor Author

@Skn0tt Skn0tt May 15, 2023

Choose a reason for hiding this comment

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

that's good context, thanks! If the on-demand revalidation stuff is still flagged, then we should definitely remove the second flag here to make things less complex. I opened a follow-up: #2113

@@ -48,6 +47,8 @@ const makeApiHandler = ({ conf, app, pageRoot, page, NextServer }: MakeApiHandle
require.resolve('./pages.js')
} catch {}

const NetlifyNextServer: NetlifyNextServerType = getNetlifyNextServer(NextServer)
Copy link
Contributor

Choose a reason for hiding this comment

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

commit messages mention trial and error process when this was added here - is this actually needed in api handler or just leftover from trials?

If it's needed - (not for this PR) I do wonder wether we should try to merge getApiHandler and getHandler into one as they become very similar now (not exactly the same) in future to make it easier to maintain going forward

Copy link
Contributor Author

@Skn0tt Skn0tt May 11, 2023

Choose a reason for hiding this comment

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

this is needed - the underlying problem was that if the plain NextServer impl is used, then revalidate fails because we're overriding the revalidate impl in NetlifyNextServer. In the past, getApiHandler was only used for scheduled functions + background functions, which won't use revalidate, so this wasn't a problem.

Agree that we should try to merge the two going forward. I've opened #2102 to track that work.

@fool
Copy link

fool commented Jun 15, 2023

Can you let us know when it is safe for us to ask customers to test this functionality? We think that it still has pieces that aren't here yet based on the above listing of PR's and issues that mention this, but maybe I am misreading?

image

@orinokai
Copy link
Contributor

hi @fool, this work is now released in v4.38.0 and i believe @Skn0tt will be rolling it out later this week and can advise on when that's complete

@fool
Copy link

fool commented Jun 23, 2023

@Skn0tt please let us know when we can tell enterprise customers they can test this for us - I had assumed it was already rolled out to all, since an enterprise customer was auto-upgraded to 4.38.1 on wednesday (cf https://github.com/netlify/pillar-support/issues/568), but now seeing that this was not @orinokai 's understanding, so am looking for the authoritative answer :)

@Skn0tt
Copy link
Contributor Author

Skn0tt commented Jun 26, 2023

If you really want to test this out, then you can set the NEXT_SPLIT_API_ROUTES=true env var to opt-in to the change.

Having said that, this will only impact API Routes, and most likely it won't help with large individual files, like the 2GB .mdb file you mention in https://github.com/netlify/pillar-support/issues/568. AWS Lambda still has a 250mb limit, and 2GB won't ever fit in there, no matter how good our bundling gets.

@fool
Copy link

fool commented Jun 26, 2023

OK. I am hearing the implication that this work might not change much today for many users. But, presumably folks for whom it breaks something, they will let us know, so it is a good test for folks to try out if they have been struggling with larger lambdas just to see if it helps and ensure it doesn't hurt!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature code contributing to the implementation of a feature and/or user facing functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants