React server-side and client side rendering not seamless - express

First the page was rendered by the server, then on client/ browser side, the Javascript script re-render the whole page!
I don't think this is how it's supposed to be since it's very bad user experience.
One thing I noticed is that the data-reactid attribute of my root element as rendered by server is some hash like .2t5ll4229s and all the children has that as prefix e.g. .2t5ll4229s.0 (the first child). Whereas, on the browser side, the data-reactid is .0 for the root element and .0.0 for the first child.
If data-reactid is really the culprit here, is there a way to set it a value of choice like eric123 for both client side and server side.
If data-reactid is not the culprit here, how do I go about making server and client side rendering of React seamless i.e. only certain elements should be re-rendered by the client side, not everything!?
My index-server-local.html template:
...
<body>
<div id="content" class="container-fluid wrapper no-padding-left no-padding-right">
{{{reactHtml}}}
</div>
<script src="bundle.js"></script>
</body>
...
My server.js:
server.get('*', function (req, res) {
console.log('request url', req.url);
log.debug('routes are', JSON.stringify(routes));
res.header("Access-Control-Allow-Origin", "*");
match({routes, location: req.url}, (error, redirectLocation, renderProps) => {
if (renderProps) {
let htmlStr = React.renderToString(<RoutingContext {...renderProps} />);
res.render('index-server-local', { reactHtml: htmlStr });
}
}
My browser.js:
React.render(<Router history={history} routes={routeConfig} />, document.getElementById('content'));
I'm using react-router 1.0.0 and React 0.13.3.

We need serialize data(or state) in server side, and send it to client side to deserialize, otherwise, the data in client side is different with the moment server render it. it will reload for sure.
One exception: pure static page, in this case React recommend us use renderToStaticMarkup
Similar to renderToString, except this doesn't create extra DOM attributes such as data-react-id, that React uses internally. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save lots of bytes.
So, how we serialize - deserialize?
Here is a simple version:
in your index-server-local.html template:
<script src="bundle.js"></script>
to:
<script dangerouslySetInnerHTML={{{{__html: 'window.__data=' + JSON.stringify({key: 'value'})}}}} />
<script src="bundle.js"></script>
And in client side, we can use __datadata now. how to map the data to your component it's based on your choice.
I recommend Reudx for this:
createStore(browserHistory, initialState)
And then
<Provider store={store}>
{ component }
</Provider>

Related

What is the point of app directory inside next.js 13?

I am trying to set up next-auth inside next.js 13. In order to be able to use the useSession in app directory, I have to convert the page to a client page by using use client.
Then I need to wrap _app.js with SessionProvider and layout file in app directory
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
)
}
and RootLayout in app component
export default function RootLayout({ children }) {
return (
<html>
<head />
<body>
<SessionProvider>{children}</SessionProvider>
</body>
</html>
);
}
Now I am repeating myself twice and I have to convert server page to a client if I want to use next-auth. Is there still any benefit of using app directory in this project? In the future, I might have a similar setup for different purposes.

Nuxt3 useFetch only retrieving data occasionally

I'm a little bit confused with Nuxt 3 and the lifecycle of when it gets data. I understand that it's a universal rendering process, but I'm using Strapi 4 to manage content in my Nuxt 3 project and only occasionally do I retrieve the data via useFetch. The API route from Strapi never goes down so I'm probably just doing something wrong.
Here is my Vue file in Nuxt:
<script setup lang="ts">
const {data: works, pending, error} = await useFetch("http://localhost:1337/api/works", {
params: {
populate: "*"
}
});
</script>
<template>
<div>
<div v-for="work in works">
... do something
</div>
</div>
</template>
I'm not sure how to get the content when the page loads. When I log the error returned, it's just true. But it's only sometimes. The content will load once, and then as soon as I refresh the page, it goes back to having an error. So I'm thinking something is getting cached maybe client-side? I'm really not sure what to do next.
Try passing this option to useFetch: initialCache: false. See more

sending POST request to express route - after receiving form data, res.render is not triggered

I'm trying to create a simple app where a picture gets uploaded, and that picture is drawn on html canvas so that i can do some simple pixel manipulation.
Right now I have the GET method for root render an EJS template with a fileReader and a canvas.
With code attached at the bottom of the EJS file through script tags, I draw the uploaded image onto the canvas so I can read each pixel's rgb values.
I then tried to send those rgb values to the POST route in the app (through fetch), but it's not working as expected.
app.post("/", (req, res)=>{
console.log("inside post");
console.log(req.body);
res.render("test", {result: req.body});
console.log("after res.render");
});
All three of the console logs print correctly in the terminal, including the request body, but the test template is not being rendered. It just stays on the same "index" view the app launches with.
Can someone give me some insight as to why this is happening? I also included console logs inside the script tags in the ejs template, and these are only displayed in the browser, not in the terminal I launch the express app with. How can I render the view inside the post method?
First
If you use AJAX like Fetch API or XHR, browser will not render the test page.
Because it's asynchronous, and you could see Ajax in MDN web docs.
You need to use form post with following code.
<form action="/" method="post">
<button type="submit">go to another page</button>
</form>
But, if you use form post, your page which might be "index.ejs" will be replaced with "test.ejs".
In other words,
Browser uses the response from the forms POST request to load the new page.
But browser pass AJAX request's response to a callback and trigger callback in js.
Browser handle these two type request (Form Post and AJAX POST) with different ways.
In common, both are sending data to server.
So, in your case, res.render is triggered successfully.
Let me show you an example. Here is my server code.
const express = require('express')
const app = express()
app.set("view engine", "ejs")
app.get("/", (req, res, next) => {
res.render("test")
})
app.post("/test", (req, res, next) => {
res.render("other-test")
})
app.listen(3000)
<!-- test.ejs -->
<h1>this is test pages.</h1>
<!-- other-test.ejs -->
<h1>this is other test pages.</h1>
When I type url http://localhost:300, browser show me this.
And I open console in chrome and type following code.
fetch("/test", {
method: 'POST', // or 'PUT'
body: JSON.stringify({}), // data can be `string` or {object}!
}).then(res => {console.log("trigger response")})
Then go the network tab in chrome, you will see the request.
Here, this request trigger the express method.
But, what is the response?
Well, it's a html. That means res.render("other-test") is triggered correctly.
And you will find the console output show "trigger response" which callback is triggered in my fetch.
And, page still stay in "test.ejs".
Next, I add following code in my test.ejs
<form action="/test" method="post">
<button type="submit">Go to other page</button>
</form>
Page will be like this.
After you click, you will find out the browser show you "other-test" content.
That's a difference between form post and ajax post.
Second
You put script tag into ejs template.
Express will use ejs engine to render your ejs template become to html page.
After it become to html page, it means all script is running in browser not your nodejs terminal.

Load a different page in nuxt at runtime based on the parameters in the route

We have a scenario in which a different page is required to be loaded based on whether parts of the route has parameters that are valid and that can be determined at run-time.
Consider the following example:
Request to http://example.com/param1/param2
If param1 is a valid product identifier (can be determined by an API call to another service) the product page loads or its considered a category and Category Page is loaded.
Considering Nuxt uses static routes mostly and the list of products are dynamic, is there a hook where you can execute custom code to load a different page ?
Cant you create _product page
like described in nuxt docs:
https://nuxtjs.org/guide/routing/#dynamic-routes
And in your code make something like:
<template>
<div>
<nuxt-child />
</div>
</template>
<script>
export default {
asyncData({route, params, redirect}) {
//use route
console.log(route.params.slug)
//directly use params
console.log(params.slug)
redirect(`/`);
},
};
</script>
or use mounted() hook if you are creating SPA

Vue prerender flickering

I have the following solution now:
<template>
<section id="prod-main">
<prod-preview v-for="prod in products" :id="prod.id" :key="prod.id"/>
</section>
</template>
export default {
...
computed: {
products: function () {
return this.$store.getters['products/getPreview']
}
}
...
}
Vuex store will receive info after some delay from my backend. So at first call it will be empty. Now I want to use vue spa prerender and here I see a flickering.
As I understood it works like:
1. Browser loads HTML with products
2. Execute js that replace products with nothing because the store is empty.
3. After some delay shows it again with backend info.
How can I fix it? I should left prerender for indexing and I can't hardcode the backend reply.
You can use the setting captureAfterTime to wait for your async call to complete, before saving the html of the page.
Other settings are available :
// NOTE: Unless you are relying on asynchronously rendered content,
// such as after an Ajax request, none of these options should be
// necessary. All synchronous scripts are already executed before
// capturing the page content.
// Wait until a specific event is fired on the document.
captureAfterDocumentEvent: 'custom-post-render-event',
// This is how you would trigger this example event:
// document.dispatchEvent(new Event('custom-post-render-event'))
// Wait until a specific element is detected with
// document.querySelector.
captureAfterElementExists: '#content',
// Wait until a number of milliseconds has passed after scripts
// have been executed. It's important to note that this may
// produce unreliable results when relying on network
// communication or other operations with highly variable timing.
captureAfterTime: 5000,
Another issue can be related to how the prerendered HTMl gets hydrated, i've openned an issue on github, but they still haven't addressed it (and are not willing to ?)
https://github.com/chrisvfritz/prerender-spa-plugin/issues/131
The solution is to add data-server-rendered="true" to your vuejs parent node in the prerendered html, like this:
<div id="root" data-server-rendered="true">...
You can use the option postProcessHtml to do so.
I don't know if I understand your problem here but have you tried to add a v-if to avoid flickering:
<template>
<section id="prod-main">
<prod-preview
v-if="products.length > 0"
v-for="prod in products"
:id="prod.id"
:key="prod.id"/>
</section>
</template>