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: static build caching for server functions #2926

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

tannerlinsley
Copy link
Collaborator

@tannerlinsley tannerlinsley commented Dec 4, 2024

This adds a new concept of "server function type". By default, all server functions are dynamic, meaning they will reach out to the server to execute. This addition brings a new type of static, which can be configured in a few places:

As the default

createServerFn({ type: 'static' })

As an dynamic function of the data/context

createServerFn().type(ctx => ctx.data > 5 ? 'static' : 'dynamic')

At the call site of the serverFn

const myFn = createServerFn()

myFn({ data: '...', type: 'static' })

It falls into this order with the rest of the builder API:

createServerFn()
  .validate(...)
  .middleware(...)
  .type(...)
  .handler(...)

How it works

  • Development
    • Everything functions as a normal server function with zero caching
  • Prerendering
    • Client
      • Each invocation of the server function is determined if it is static or dynamic
      • The type is sent to the server with the request
    • Server
      • The server function operates normally
      • If static, the result of the server function is stored in the public output as a static JSON file
    • Client
      • The result is received as normal
  • Production
    • Client
      • Each invocation of the server function is determined if it is static or dynamic
      • If the server function is dynamic, the request is handled normally
      • If the server function is static, the request is rerouted to fetch the static JSON file

Customizing the serverFnStaticCache

You can create and set your own serverFnStaticCache by using the createServerFnStaticCache and setServerFnStaticCache functions.

Imagine we might want to cache the output of the server function to giant massive JSON file (don't do this). We could do something like this:

setServerFnStaticCache(() => {
  const cacheFile = path.join(process.cwd(), 'public/static-cache.json')
  const getKey = (ctx: MiddlewareCtx) => {
    return `${ctx.filename}-${ctx.functionId}-${JSON.stringify(ctx.data)}`
  }

  return createServerFnStaticCache({
    getItem: async (ctx) => {
      const key = getKey(ctx)
      const data = await readFile(cacheFile, 'utf8')
      const json = JSON.parse(data)
      return json[key]
    },
    setItem: async (ctx, response) => {
      const key = getKey(ctx)
      const data = await readFile(cacheFile, 'utf8')
      const json = JSON.parse(data)
      json[key] = response
      await writeFile(cacheFile, JSON.stringify(json, null, 2))
    },
    fetchItem: async (ctx) => {
      const key = getKey(ctx)
      const data = await fetch('/static-cache.json').then((res) => res.json())
      const json = JSON.parse(data)
      return json[key]
    },
  })
})

Imagine the other places you could technically create this cache:

  • Database
  • CDN
  • File System
  • In-memory Datastore (like Redis)

More cool things

  • You get middleware for free since it's based on server functions
  • Any server context that you send back to the client is also saved with the JSON
  • If errors are thrown, they are saved to the JSON as well under an error key
  • The Start serializer is used, which means dates and undefined are properly serialized.

Copy link

nx-cloud bot commented Dec 4, 2024

☁️ Nx Cloud Report

We didn't find any information for the current pull request with the commit b1fd9ef.
Please verify you are running the latest version of the NxCloud runner.

Check the Nx Cloud Source Control Integration documentation for more information.

Alternatively, you can contact us at [email protected].


Sent with 💌 from NxCloud.

packages/start/src/client/createServerFn.ts Outdated Show resolved Hide resolved
@@ -18,6 +18,7 @@ import { config } from 'vinxi/plugins/config'
import { serverFunctions } from '@vinxi/server-functions/plugin'
// @ts-expect-error
import { serverTransform } from '@vinxi/server-functions/server'
import { createNitro } from 'nitropack'
Copy link
Member

Choose a reason for hiding this comment

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

In the package.json we should move this from devDependencies to dependencies.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, we should

packages/start/src/config/index.ts Show resolved Hide resolved
@@ -0,0 +1,31 @@
{
"name": "tanstack-start-example-basic-static",
Copy link
Member

Choose a reason for hiding this comment

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

Keeping this here as a reminder that we should duplicate this example into the e2e/start directory and run the end-to-end test on the built output.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea

})
}

function getStaticCacheUrl(options: MiddlewareCtx, hash: string) {
return `/__tsr/staticServerFnCache/${options.filename}__${options.functionId}__${hash}.json`
Copy link
Member

Choose a reason for hiding this comment

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

The base needs to be customizable since we've had people host their app on a subpath. It'd just be a matter of injecting the values from start's config as env vars like done for getBaseUrl impl for server-fns.

Copy link

netlify bot commented Dec 6, 2024

Deploy Preview for start-basic-static failed. Why did it fail? →

Name Link
🔨 Latest commit b1fd9ef
🔍 Latest deploy log https://app.netlify.com/sites/start-basic-static/deploys/6757331d7472460008309123

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.

3 participants