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

New hook onBoot() #962

Open
brillout opened this issue Jun 19, 2023 · 27 comments
Open

New hook onBoot() #962

brillout opened this issue Jun 19, 2023 · 27 comments

Comments

@brillout
Copy link
Member

brillout commented Jun 19, 2023

Description

A common use case is to fetch global initialization data that is needed by all pages. (For example i18n data.)

// /pages/+onBoot.js

export default onBoot

async function onBoot() {
  const someInitData = await fetchSomeInitData()
  return { pageContext: { someInitData } }
}

The onBoot() hook is executed as soon as the server is started and its result is awaited before any page starts rendering.

It's executed when the production/development/preview server starts as well as when the pre-rendering process starts. So it's guaranteed to have been executed before any page starts (pre-)rendering.

Workaround until this is implemented:

  • Add your initial data at renderPage(), see API > pageContext > Custom.
  • Use a single global onBeforeRender() hook that applies to all your pages, and fetch your global/init data in there.
  • If you need per-page data fetching: create a new custom hook (or a custom config if you use the V1 design) called onDataLoad(). Call onDataLoad() from your global onBeforeRender() hook.

Additional consideration:

  • Also for the client-side, by defining +onBoot.client.js.
    • +onBoot.js => server-side only.
    • +onBoot.client.js => client-side only.
    • +onBoot.shared.js => server- and client-side. (I can't think of a use case, but supporting .shared wouldn't induce any additional implementation cost.)
    • You could even define both +onBoot.js and +onBoot.client.js.

See also:

If not having onBoot() is a blocker for you then write a comment down below.

@brillout brillout added the enhancement ✨ New feature or request label Jun 19, 2023
@gdev219
Copy link

gdev219 commented Jun 22, 2023

that is great I was customizing the express server and fetch some data before it renders the page. this will be very helpful thanks!

@brillout
Copy link
Member Author

brillout commented Jun 22, 2023

@gdev219 👍 Let me know if not having this hook is a blocker. (Since you have an Express.js server, you should be able to easily workaround it.)

@brillout brillout changed the title New hook onServerStart() New hook onStart() Jun 27, 2023
@brillout brillout changed the title New hook onStart() New hook onStartup() Aug 14, 2023
@s3ththompson
Copy link

s3ththompson commented Nov 13, 2023

This feature would be very helpful! I'd like to fetch navigation menu entries and default site metadata (title, description, etc.) from a CMS, before any page loads.

Alternatively, if the nested layout design is powerful enough, the onBoot hook, could simply be an onBeforeRender hook in a root-level layout applied to all pages. In some ways, I think this would actually be more elegant than onBoot (but I wouldn't mind having both).

@stampaaaron
Copy link

@brillout That feature would be very useful. Tbh I'd be fine with the workaround of having a global onBeforeRender hook as well, but I don't seem to get that working. Using this approach I'm trying to make enum objects available for all Pages in the pageContext. I added a +onBeforeRender file to the /pages root but I in the actual pages I don't receive the data. The file looks like this:

import { enumObjects } from '../renderer/types';

export default onBeforeRender;

async function onBeforeRender() {
  return {
    pageContext: {
      enums: enumObjects,
    },
  };
}

In the Page I'm accessing the pageContext using a usePageContext hook:

...
  const { enums } = usePageContext();

  console.log(enums); // retrieves undefined
...

Do you have an idea what I'm doing wrong here?
(btw. I'm using V1 Design... really like it!)

@phonzammi
Copy link
Member

I added a +onBeforeRender file to the /pages root but I in the actual pages I don't receive the data.

Try to move +onBeforeRender file to /renderer folder (outside /pages folder)

- /pages
- /renderer/+onBeforeRender.ts

I hope this help you

@stampaaaron
Copy link

@phonzammi Thanks for the tip. I tried that, but when debugging I noticed, that the onBeforeRender function doesn't seem to be called at any time. It's just being ignored. Any further ideas?

@phonzammi
Copy link
Member

phonzammi commented Nov 14, 2023

Do you have any +onBeforeRender file inside some pages folder? E.g. /pages/posts/index/+onBeforeRender.ts. If so then remove it

Take a look at my example here

If you can give a reproduction may help too

@brillout
Copy link
Member Author

@s3ththompson FYI the workaround should also work for your use case.

@stampaaaron
Copy link

Do you have any +onBeforeRender file inside some pages folder? E.g. /pages/posts/index/+onBeforeRender.ts. If so then remove it

@phonzammi what if I still want to retrieve specific data for one page, but have global data available for all pages? Isn't that the whole point of this Issue?

@phonzammi
Copy link
Member

phonzammi commented Nov 16, 2023

what if I still want to retrieve specific data for one page, but have global data available for all pages? Isn't that the whole point of this Issue?

@stampaaaron. In my opinion, that case should refer to state management or cache, you don't want/need to fetching then refetching global data again and again right? or if you need too then you can add async state management library such as tanstack-query or swr, etc.

Please read again

Workaround until this is implemented:

  • Use a single global onBeforeRender() hook that applies to all your pages, and fetch your global/init data in there.
  • If you need per-page data fetching: create a new custom hook (or a custom config if you use the V1 design) called onDataLoad(). Call onDataLoad() from your global onBeforeRender() hook.

First thing first, it's about fetching (or refetching) global/init data. Then it's up to you how to handle/persist/preserve that data.

Actually, I still have some answers or opinions (some still in my mind & I haven't implement yet).

What do you think @brillout ?
cmiiw

@brillout
Copy link
Member Author

@phonzammi Yes, custom hooks seems to be what @stampaaaron is looking for.

@lourot
Copy link
Contributor

lourot commented Dec 27, 2023

what if I still want to retrieve specific data for one page, but have global data available for all pages? Isn't that the whole point of this Issue?

@stampaaaron maybe helpful: we have now invented a new data() hook, very similar to the onBeforeRender() hook. So you can now have a global onBeforeRender() hook and a page-specific data() hook.

@brillout brillout changed the title New hook onStartup() New hook onBoot() Jan 5, 2024
@brillout
Copy link
Member Author

brillout commented Jan 5, 2024

New name: onBoot(). I'm finally happy with the name (but feel free to disagree and push back).

@OlaviSau
Copy link
Contributor

OlaviSau commented May 14, 2024

@brillout
+client and having onBoot run in the client are not equivalent as the +client does not modify the pageContext as far as it seems by docs.

If you wanted to set a i18n values for onBeforeRoute, which runs both in the client and in the server - you would have 2 different sources for these values. Ideally there would be some clean way to persist something permanently in the pageContext because that would be enough for this use case as the user can create the initial pageContext for the renderPage. Maybe something like staticContext / globalContext / appContext.

@brillout
Copy link
Member Author

@OlaviSau Indeed. I just updated the Feature Request.

@OlaviSau
Copy link
Contributor

Do you want me to implement it as you specified in the update?

@brillout
Copy link
Member Author

@OlaviSau Do you want to implement it because you need it or because you want to contribute? If it's only because you need it it's probably faster I implement it. But if you want to contribute on a recurring basis, then yes we can give a collaboration a shot! (Btw. I like that one blog post of yours.)

@OlaviSau
Copy link
Contributor

OlaviSau commented May 14, 2024

Do you want to implement it because you need it or because you want to contribute?

I do "need" it in the sense that it would make our code simpler and I really don't mind implementing it either. I suspect that this won't be the last contribution, especially after that kind of flattery 😁 😁

Though there are certain design questions that pop up for sure that you might be able to answer more easily when implementing it yourself.
Some that come to mind:
Should the file naming convention define whether the hook is ran in the server / client / shared or should it be via the config?
When exactly does a vike application "boot" if the entry point is by express? Is it when renderPage is called or should it be earlier? Would it even be supported in this case since render page can be called multiple times?

We use express yet it would still be very nice to have this, even though there are workarounds. It wouldn't be fair to say that it's a blocker though.

@brillout
Copy link
Member Author

Ok let's give it a try then!

One of these contribution-welcome 💕 would be easier, but we can try and if we realize this feature request is a little too ambitious then I can take over the implementation.

As for the design questions, we'll see as we go. Note https://vike.dev/file-env which is a fairly recent feature that makes a lot of sense in this context. And, yes, it should be as soon as Vike's runtime loads, i.e. regardless of whether the user uses Express.js.

after that kind of flattery

I see 😁 - no but seriously: you figured out a fundament of good SWE which makes me think you have taste.

@OlaviSau
Copy link
Contributor

I'll give it a try 😁 Thanks for the file-env reference, would have been huge problem if I would have missed that ❤️

@codefln
Copy link
Sponsor

codefln commented Jun 3, 2024

This is a great idea, I would be very happy if this hook was implemented

@brillout
Copy link
Member Author

brillout commented Jun 4, 2024

@codefln Feel free to elaborate your use case.

@brillout
Copy link
Member Author

brillout commented Jun 4, 2024

In general, the more use cases for onBoot() I know the better.

@codefln
Copy link
Sponsor

codefln commented Jun 4, 2024

@codefln Feel free to elaborate your use case.

My use case is almost identical to the author of this issue.

@TimJohns
Copy link

TimJohns commented Jul 9, 2024

Very similar to the original use case, but with with services. We currently use a number of services that have identical interfaces (but distinct implementations). We were providing them in +onCreateApp, which worked great specifically for Vue components, but we recently moved some of that logic to some of our pages' data() hooks (which are called before +onCreateApp). We have a workaround by lazy-loading them and caching them in a few places, but I think it would be a little cleaner and more deterministic for performance to load them in onBoot.

@brillout
Copy link
Member Author

Very similar to the original use case, but with with services. We currently use a number of services that have identical interfaces (but distinct implementations). We were providing them in +onCreateApp, which worked great specifically for Vue components, but we recently moved some of that logic to some of our pages' data() hooks (which are called before +onCreateApp). We have a workaround by lazy-loading them and caching them in a few places, but I think it would be a little cleaner and more deterministic for performance to load them in onBoot.

So, depending on the page, you want to use a different onBoot() hook, correct?

Note

With @OlaviSau, author of the onBoot() draft PR, we were wondering whether onBoot() should be global (i.e. whether there can be only one global onBoot() hook). I'm thinking maybe we should introduce two different hooks, e.g. a per-page onRequest() in addition to a global onBoot().

@TimJohns
Copy link

TimJohns commented Jul 10, 2024

So, depending on the page, you want to use a different onBoot() hook, correct?

For my specific use case, the services to instantiate are determined at build time (or dev launch time), so a single global onBoot would work great. The services are intended to be initialized once, then usable by any page's data hook. I can also envision similar use cases where the per-page onRequest might also make sense -- for instance, if the services' implementation differ by page, or as an optimization if we were providing initialization-heavy services or data specifically for a subset of rarely used pages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants