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

Add a <server> block to SFC to combine server code with client code in one file. #20802

Closed
2 of 4 tasks
HendrikJan opened this issue May 12, 2023 · 12 comments
Closed
2 of 4 tasks

Comments

@HendrikJan
Copy link

HendrikJan commented May 12, 2023

Describe the feature

Currently you can create an API in the /server folder and then consume the API in your components (/pages or /components).
This means:

  • You have to write code in two places.
  • You have to think about how to design the API.

With this proposed feature you could instead combine this in one SFC file:

<server>
    // Pseudo code
    const users = DB.query("SELECT * FROM Users");
    export { users };
</server>

<script setup>
    defineServerProps({ 
        users: Object,
    });
</script>

<template>
    <h1>List of users</h1>
   <ul>
        <li v-for="user in users">
            User: {{ user.name }}, age: {{ user.age }}
        </li>
    </ul>
</template>

This should work both with SSR as in the client without the programmer noticing any difference.

On the server (SSR):
When run on the server, the variables exported in the <server> block would directly be available in the <script> block.

On the client:
When run on the client, an Ajax call will automatically be made (without the user having to program anything) to get the variables from the server.
This way the code in the <server> block will always run on the server, while the exported variables will always be available in the <script> block.

Inspiration:
I was inspired by Inertia.js where Laravel computes variables that become available in the component props. This works both when Laravel renders the SFC on the server (the variables are injected somehow) as on the client (some magic transfers the variables from the server to the client).

What makes this feature usefull?

  • The code that produces data on the server and the code that consumes the data on the client are in one file, which makes it easier to keep both in sync.
  • You don't have to think about how to design your API
  • This feature does not break any existing functionality (as far as I can see)

What could be downsides?

  • SFC files become larger
  • This feature has to be build by someone

Additional information

  • Would you be willing to help implement this feature?
  • Could this feature be implemented as a module?

Final checks

@15844229646
Copy link

I am currently using nuxt-server as well

If this method is used, how can we solve the client rendering problem of nuxt-link,

Nuxt is currently only the first screen SSR. After nuxt-link jumps, there is no DB. query ("SELECT * From Users") on the client;

@HendrikJan
Copy link
Author

I am currently using nuxt-server as well

If this method is used, how can we solve the client rendering problem of nuxt-link,

Nuxt is currently only the first screen SSR. After nuxt-link jumps, there is no DB. query ("SELECT * From Users") on the client;

After a nuxt-link jump, the code DB.query("SELECT * FROM Users") would run on the server and an Ajax call to receive the resulting users-array would automatically be initiated in the client without you, the programmer, to have to do anything.
Nuxt could for instance use the beforeMount-hook to automatically initiate this call.

@HendrikJan
Copy link
Author

I think this would have some of the same benefits as server components:

  • less JavaScript send to the browser (for instance a Markdown library doesn't have to be send to the browser, as the Markdown compilation can be done in the <server> block)

The difference with server components:

  • Json payload is send on server updates, instead of rendered html
  • No need to have .server.vue and .client.vue matching files, as you combine server and client logic in one file

@HendrikJan
Copy link
Author

Instead of <server> it could also be <script server-only> which might convey the intended use better.
Also, then we can also add a <script client-only> and probably <template server-only>.

@danielroe
Copy link
Member

I think this is a great candidate for implementing in user-land with a Nuxt module.

I've done something similar as an experiment which I plan to open-source soon via a PR to https://github.com/TanStack/bling.

@Hebilicious
Copy link
Member

Referencing comment on suggested API : #19772 (comment)

@Hebilicious
Copy link
Member

@HendrikJan In case you're looking for something similar, I just found out about a really interesting module that does this : https://www.npmjs.com/package/numix

I'm thinking about using this idea and adding it into https://github.com/Hebilicious/form-actions-nuxt

@Hebilicious
Copy link
Member

Initial implementation https://github.com/Hebilicious/server-block-nuxt is out 🎉 Please try it and leave feedback. PR welcome !

@BananaAcid
Copy link

BananaAcid commented Jul 13, 2023

@Hebilicious I think it is an interesting and nice take on the subject.
The only inconvenience i see is with the export. Having methods like patch, head, delete and more ... as well as having to write a fetch function for all of them again ... (defineServerProps was a nice idea.)

I think, for the current version of server-block-nuxt having an export object with the keys as the HTTP Methods would be nice.

Additionally, I feel like the default generated api routes could be clashing with existing user ones quite too often (without using the path attribute), due to ending up as simple route names. (thinking of /api/index.ts => echoing 'This is the API backend v1.0.0' - and - /pages/index.vue <server> export const GET ...)

As a side note: I am thinking about something like useServerGet() / useServerPost({body: {a:1}, headers:...) to wrap useFetch and where the route is automatically abstracted from the current file and does not need to be specified, and additional other params are just passed on to useFetch ... This could allow any naming scheme for the routes - api/_pages/example.ts or alike

Question: How would a route like /api/books/[artist]/[id] be defined?

@Hebilicious
Copy link
Member

Hebilicious commented Jul 13, 2023

@Hebilicious I think it is an interesting and nice take on the subject. The only inconvenience i see is with the export. Having methods like patch, head, delete and more ... as well as having to write a fetch function for all of them again ... (defineServerProps was a nice idea.)

I think, for the current version of server-block-nuxt having an export object with the keys as the HTTP Methods would be nice.

Additionally, I feel like the default generated api routes could be clashing with existing user ones quite too often (without using the path attribute), due to ending up as simple route names. (thinking of /api/index.ts => echoing 'This is the API backend v1.0.0' - and - /pages/index.vue <server> export const GET ...)

As a side note: I am thinking about something like useServerGet() / useServerPost({body: {a:1}, headers:...) to wrap useFetch and where the route is automatically abstracted from the current file and does not need to be specified, and additional other params are just passed on to useFetch ... This could allow any naming scheme for the routes - api/_pages/example.ts or alike

Question: How would a route like /api/books/[artist]/[id] be defined?

Thanks for the feedback @BananaAcid

  • defineServerProps : Instead of defineServerProps I want to combine this approach with the data loader pattern, and have a useLoader function.

  • export objects : Since you can use named exports, you can do export { GET, POST }

  • Clash with existing routes : This is un-avoidable if we offer a different way to define api routes. The defaultPrefix can be configured to something else than api, but I'm not sure that's a good idea.

  • I think the proper way to solve this is form actions and loaders https://github.com/Hebilicious/form-actions-nuxt I definitely want to push in that direction, but this needs more community support.

  • With the path argument

On a side note regarding #21370 , I would like to see the API routes on the manifest.

@Hebilicious
Copy link
Member

@danielroe Can we consider this completed with the server block module ? Maybe in the future this can be brought directly into Nuxt, but I haven't seen that much interest/usage, so it's probably fine as a module ?

@danielroe danielroe closed this as not planned Won't fix, can't repro, duplicate, stale Sep 6, 2023
Copy link
Member

Feedback welcome on this if anyone uses and finds it helpful 🙏

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

5 participants