简体   繁体   中英

Server side rendering with head

Is it possible to do server side rendering only to <head></head> data? I am trying to use feature like next.js:

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

So, in the express server it will catch the exported function getServerSideProps and set the data in the <head> tag. I don't want every other features of next.js and thus I don't want to use next.js. Is it even possible?

In order to server-side render react app without using any SSR framework (next.js,remix.run etc), you have to change the architecture of your app. Because in a single-page application (SPA), browser downloads the empty HTML, parses it and when sees the script, it makes another request to download the bundled javascript code.

To create a function like getServerSideProps you have to write a logic to tell your app that before you serve any page, make sure if there is a specific named function (you define it) run this function first. For this, you need to configure your react router differently. Configuration of react-router-route-config . you could also use different npm packages but finding good documentation would be hard

To learn more about how server-side react app is implemented please read this in-depth article react-router-server-rendering .

  • You said you do not want to use Next.js but after reading those two articles, you will understand why server-side react frameworks are so popular.

after this, I will quote from the article above to show you how to render only the head

import Home from "./Home";
import Grid from "./Grid";
import { fetchPopularRepos } from "./api";

    const routes = [
      {
        path: "/",
        component: Home,
      },
      {
        path: "/popular/:id",
        component: Grid,
        // this is like getServerSideprops function. 
        // whatever logic is
        fetchInitialDataCustomFunction: (path = "") => fetchPopularRepos(path.split("/").pop()),
      },
    ];
    
    export default routes;

server/index.js

import { matchPath } from "react-router-dom"
import routes from '../shared/routes'

app.get("*", (req, res, next) => {
  const activeRoute = routes.find((route) =>
    matchPath(route.path, req.url)
  ) || {}

})

Now, activeRoute will be the route of whatever page the user was requesting (req.url).

The next step is to see if that route requires any data. We'll check if the activeRoute has a fetchInitialData property. If it does, we'll invoke it passing it the current path, if it doesn't, we'll just continue on.

Now this is the part where you will implement to send only head

app.get("*", (req, res, next) => {
  const activeRoute =
    routes.find((route) => matchPath(route.path, req.url)) || {};

  const promise = activeRoute.fetchInitialData
    ? activeRoute.fetchInitialData(req.path)
    : Promise.resolve();

  promise
    .then((data) => {
      const markup = ReactDOM.renderToString(<App serverData={data} />);

      res.send(`
      <!DOCTYPE html>
      <html>
        // this is the head part that you want to implement
        <head>
          <title>SSR with React Router</title>
          <script src="/bundle.js" defer></script>
          <link href="/main.css" rel="stylesheet">

          ------------------------------------------------
          // this part is usually used for redux.
          // we send the data store to the client, so the client will use it as the initial state of the app
          <script>
            window.__INITIAL_DATA__ = ${serialize(data)}
          </script>
        </head>

        // you do not want this part. this is the main content is sent to the client
        <body>
          <div id="app">${markup}</div>
        </body>
      </html>
    `);
    })
    .catch(next);
});

Something you could try is using a library like cheerio or a headless browser like Puppeteer to parse the HTML and extract only the head element. Below is an example using cheerio :

import cheerio from 'cheerio';

export async function getServerSideProps(context) {
  // Load the page HTML
  const res = await fetch(`http://localhost:${process.env.PORT}${context.pathname}`);
  const html = await res.text();

  // Use cheerio to parse the HTML and extract the head element
  const $ = cheerio.load(html);
  const headHtml = $('head').html();

  return {
    props: {
      headHtml,
    },
  };
}

I'm not sure if this is exactly what you were looking for, but if so I hope it helps.

Question: Is it possible to do server-side rendering only to data?

Yes, it is possible!

by using server-side rendering technology, such as Node.js and Express.

Follow these steps to achieve this:-

  • Create a route in your Express server that will handle the request for the page with the <head> element you want to render.

  • install ejs npm install ejs

  • In the route handler, use the render method of your view engine (eg EJS, Pug, etc.) to render a template with the <head> element and any other data you want to include in it.

  • use placeholders (eg <%= title %>) for the dynamic data that will be passed to the template from the route handler.


Code Walk trough

install packages:-

  npm i express ejs cors

Server code:-

server.js


const ejs = require('ejs');
const express = require ('express');
const cors = require ('cors');
const app = express ();

const PORT = process.env.PORT || 3000

app.use( 
  cors({
    origin: '*',
    credentials: false,
    methods: ['GET'],
  }),
 );

app.get('/head', (req, res) => {
  // you can use data from database
  const data = {
    title: 'My Page',
    description: 'This is my page.'
  }
  res.send(ejs.render(`
    <head>
      <title><%= title %></title>
      <meta name="description" content="<%= description %>">
    </head>
  `, data))
})

 app.listen(PORT, () => {
  console.log('Listening on port 3000');
 });

Front-end Code

Here is example using React-js

install packages:-

  npm i axios-hooks

Since OP wants to use server-side rendering only on the head that's why I'm using conditional rendering to render the page after the server response. You can aslo use server-side fetching .

App.tsx

import useAxios from 'axios-hooks'

export default App () {
  
  const [{ data, loading, error }, refetch] = useAxios(
    'https://your-api/head'
  );
  
  return (
    <>
      {(!data && loading) && <>loading...</>}
      {data ? <>App</> : <>error 404</>}
    </>
  );
}

next.js will indeed let you do that with the pre-rendering.

On every page, you can return only the <head/> without <body/> if you want, as shown here https://stackoverflow.com/a/60398652/18364469

As rendering may be quite complex, If you want a production ready solution, I suggest you to use next.js, they have tackled many issues you may not be aware of. If you don't want to use it, what are your constraints?

If you're just curious and want to re-create something similar to next.js, I suggest you reading the code on next.js, maybe starting from this file https://github.com/vercel/next.js/blob/canary/packages/next/server/next-server.ts as you're interested in server developement.

You can also setup a simple express server that returns an html document only with a <head/> tag like this.

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('<html><head></head></html>')
})

app.listen(port, () => {
  console.log(`App listening on port ${port}`)
}) 

Feel free to provide more context to get a more accurate answer

Yes, it is possible to do server-side rendering only for the data in the tag. One way to do this is to use a library like React Helmet, which allows you to easily set the data in the tag from your React components.

Here is an example of how you could use React Helmet to set the title and description in the tag:

import { Helmet } from 'react-helmet';

const MyComponent = () => {
return (
<div>
<Helmet>
<title>My Page Title</title>
<meta name="description" content="My page description" />
</Helmet>
{/* Other component content goes here */}
</div>
);
}

Then, in your express server, you can use a library like ReactDOMServer to render the React component to a string, and insert the resulting HTML into the tag of your server-side rendered page.

For example:

import ReactDOMServer from 'react-dom/server';

app.get('/', (req, res) => {
const html = ReactDOMServer.renderToString(<MyComponent />);
res.send( <!DOCTYPE html> <html> <head> ${html} </head> <body> {/* Other page content goes here */} </body> </html> );
});

This approach allows you to use server-side rendering only for the data in the tag, without needing to use a full-featured framework like Next.js.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM