react-router Link doesn't render href attribute with server side rendering? - express

I have a expressjs app with the following code, what i am trying to do is to render a react component named Hello with the request to "/". Inside Hello component i am using two react-router Links, but these two Links doesn't render the href attributes.
I am using here react-router 2
Server.js file
var express = require("express");
var app = express();
var React = require("react");
var ReactDOM = require("react-dom/server");
var Hello = React.createFactory(require("./js/components/Hello"));
// The above is a reference to pre-compiled Hello.jsx to Hello.js using babel
app.set("view engine", "ejs");
app.set("views", "./views");
app.use(express.static("./public"));
app.get("/", function (req, res) {
res.render("Home",{data:ReactDOM.renderToString(Hello())});
});
app.listen(app.get("port"), function () {
console.log("server started on port " + app.get("port"));
});
Hello.jsx File
var React = require("react");
var Link = require("react-router/lib/Link");
var Hello = React.createClass({
callMe:function(){
alert("Hurray");
},
componentWillMount:function(){
console.log("componentWillMount fired");
},
render:function(){
// console.log(this);
return(
<div>
<h2 onClick={this.callMe}>Hello Boom Boom {this.props.name}</h2>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
// above two Links rendering anchor tags without href attribute
</div>
);
}
});
module.exports = Hello;
Home.ejs File
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<h2>Welcome to home page</h2>
<div id="container">
<%- data %>
</div>
</body>
</html>

Please consult the React Router server rendering guide: https://github.com/reactjs/react-router/blob/master/docs/guides/ServerRendering.md
You need to render your components in an appropriate routing context for <Link> to generate URLs.

I just had the same problem, and the thing I was missing was the surrounding <RouterContext>. <Link> components seem to have an empty href attribute when not inside of a <Router> or appropriate react-router environment. I improved the routing in the SSR code (I was doing a weird partial solution) and the links started appearing.

Related

How to use Vue global variables in html head section?

I am using Vue version 2.6.11 in my website. I set some global variables according to this question in my src/main.js file as follow:
/* global variables */
Vue.prototype.$frontUrl = "http://localhost:8080/";
Vue.prototype.$backUrl = "http://localhost:3500/";
That works fine if I use them in my Vue views or components like this.$backUrl in this form tag:
<form novalidate class="row pl-md-5" :action="this.$backUrl + 'sql'" method="post">
<!-- some input tags ... -->
</form>
But I want to use them in my public/index.html file as the code below:
<!DOCTYPE html>
<html>
<head>
<!-- og meta tags -->
<meta name="type" property="og:type" content="website">
<meta name="image" property="og:image" :content='this.$backUrl + "img/images/portfolio-preview.png"'>
<meta name="url" property="og:url" content="">
</head>
</html>
It does not work. It does not give me any error but don't use Vue global variable value. If I see my page source in browsers, It shows me <meta name="image" property="og:image" :content='this.$backUrl + "img/images/portfolio-preview.png"'>. Could anyone please help me how to use those variables in my index.html head section?
This is a tricky question and I solved this way. All credit to this site who points the answer: link
create a definitions file definitions.js
export default {
urlTest: "localhost:8080"
}
To keep code logic you need to change the global variables definitions for something like this:
import def from '../definitions.js'
Vue.prototype.$frontUrl = def.urlTest
//Only if you really need to store as a global
then on the vue-router where you define the path:
import HomeView from '../views/HomeView.vue'
import definitions from '../config/definitions.js'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [{
path: '/',
meta: {
title: "Home",
metaTags: [{
//<meta name="image" property="og:image" :content='this.$backUrl + "img/images/portfolio-preview.png"'>
name: 'image',
property: 'og:image',
content: definitions.urlTest + "/img/images/portfolio-preview.png"
}]
},
name: 'home',
component: HomeView
}]
})
// This callback runs before every route change, including on page load.
router.beforeEach((to, from, next) => {
// This goes through the matched routes from last to first, finding the closest route with a title.
// e.g., if we have `/some/deep/nested/route` and `/some`, `/deep`, and `/nested` have titles,
// `/nested`'s will be chosen.
const nearestWithTitle = to.matched.slice().reverse().find(r => r.meta && r.meta.title);
// Find the nearest route element with meta tags.
const nearestWithMeta = to.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
const previousNearestWithMeta = from.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
// If a route with a title was found, set the document (page) title to that value.
if (nearestWithTitle) {
document.title = nearestWithTitle.meta.title;
} else if (previousNearestWithMeta) {
document.title = previousNearestWithMeta.meta.title;
}
// Remove any stale meta tags from the document using the key attribute we set below.
Array.from(document.querySelectorAll('[data-vue-router-controlled]')).map(el => el.parentNode.removeChild(el));
// Skip rendering meta tags if there are none.
if (!nearestWithMeta) return next();
// Turn the meta tag definitions into actual elements in the head.
nearestWithMeta.meta.metaTags.map(tagDef => {
const tag = document.createElement('meta');
Object.keys(tagDef).forEach(key => {
tag.setAttribute(key, tagDef[key]);
});
// We use this to track which meta tags we create so we don't interfere with other ones.
tag.setAttribute('data-vue-router-controlled', '');
return tag;
})
// Add the meta tags to the document head.
.forEach(tag => document.head.appendChild(tag));
next();
});
This is with Vue3 but with the method and router path definition has the same format.
For more detailed explanation check the link on top.
I finally found a solution with the help of Vue CLI Environment Variables. I saved my global variables values in a .env file in the root directory of project like the code below:
.env file:
VUE_APP_BACK_URL=http://localhost:3500/
Then I changed my src/main.js file that contains global variables like the code below:
main.js:
Vue.prototype.$backUrl = process.env.VUE_APP_BACK_URL;
So that I don't need to change other components and views that used this global variable. Finally for html head tag I used variables defined in .env file directly as could be seen in the code below:
<!DOCTYPE html>
<html>
<head>
<!-- og meta tags -->
<meta name="type" property="og:type" content="website">
<meta name="image" property="og:image" content='<%= process.env.VUE_APP_BACK_URL + "img/images/portfolio-preview.png"%>'>
<meta name="url" property="og:url" content="<%= process.env.VUE_APP_BACK_URL %>">
</head>
</html>

How to use Compose API in a standalone (one-file) Vue3 SPA?

I write (amateur) Vue3 applications by bootstrapping the content of the project and then building it for deployment(*). It works great.
I need to create a standalone, single HTML page that can be loaded directly in a browser. I used to do that when I was starting with Vue a few years ago (it was during the transition v1 → v2) and at that time I immediately found the proper documentation.
I cannot find a similar one for Vue3 and the Composition API.
What would be a skeleton page that would display the value reactive variable {{hello}} (that I would define in <script setup> in the context of a full, built application)
This is how I used to do it in the past (I hope I got it right)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue#2"></script>
</head>
<body>
<div id="app">
{{hello}}
</div>
<script>
// this is how I used to do it in Vue2 if I remember correctly
new Vue({
el: '#app',
data: {
hello: "bonjour!"
}
// methods, watch, computed, mounted, ...
})
</script>
</body>
</html>
(*) I actually use the Quasar framework but this does not change the core of my question.
You couldn't use script setup using the CDN, according to official docs:
<script setup> is a compile-time syntactic sugar for using Composition API inside Single File Components (SFCs)
but you could use the setup hook inside the page script as follows :
const {
createApp,
ref
} = Vue;
const App = {
setup() {
const hello = ref('Bonjour')
return {
hello
}
}
}
const app = createApp(App)
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app">
{{hello}}
</div>

Vue: How to change tab title in Chrome

How do I change the default "Webpack App" tab title in Vue.js?
In Your public/index.html or other HTML files used as templates by html-webpack-plugin. find title tag and change it
<title>Page Title</title>
Or you can set the page title dynamically by getting the title from document document.title and change it inside your component like the code below :
<template>
<div id="app"></div>
</template>
<script>
export default {
name: "App",
mounted() {
document.title = "page title";
},};
</script>
you have at least two options:
document.title = 'Your new name'
or in the index.html-file:
<head>
<title>Your new name</title>
</head>
You will find <title>Webpack App</title> in index.html in most probably public folder. You can change it
We needed to fix this in the webpack config which generated the index.html every time we deployed our Storybook app.
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = async ({ config }) => {
config.plugins.push(
new HtmlWebpackPlugin({ title: 'Storybook — Company Name' }),
)
return config
}

Web component Poly fills are not working in IE 11. I have added the poly fill JS file in index.html file

I have added the Web component Polyfill.
npm install #webcomponents/webcomponentsjs
I have added them in my index.html file:
<script src="./webcomponents/custom-elements-es5-adapter.js"></script>
<script src="./webcomponents/webcomponents-bundle.js"></script>
<script src="./webcomponents/webcomponents-loader.js"></script>
<script src="./webcomponents/webcomponents-bundle.js.map"></script>
Also Script for no suppport.
<script>
if (!window.customElements){document.write('Web components not supported'); alert('hi');
console.log('No web component');
}
I get the data Web components not supported in IE 11.
I tried the same using Vanilla JS and HTMl
My Html code
<html>
<style>
<head>
<script src="https://unpkg.com/#webcomponents/webcomponentsjs#2.5.0/webcomponents-
loader.js">
<script>
if (!window.customElements){document.write('Web components not supported');
alert('hi');
console.log('No web component');
}else{
alert('Web componnets is supported');
}
</script>
</head>
<body>
**<my-shadow></my-shadow>**
<script src="C:\Users\rgo7cob\Desktop\shadow.js"></script>
<h3> Hello. It is in Red </h3>
</body>
</html>
I have loaded the script from https://unpkg.com/#webcomponents/webcomponentsjs#2.5.0/webcomponents-loader.js.
I get the error in IE 11 console at Line number 2 in Js file
const template =document.createElement('template');
**template.innerHTML=`**
<style>
h3{
color : blue;
}
</style>
<h3> This is data from the Template and it is blue</h3>
Browser is not able to identify the template object even after using webcomponents-loader.js.
My Web component element must look like this.
When you reference webcomponents-loader.js in the page, IE will actually be compatible with the template element. Your problem is that you need to add the template element to the body after you create it, and if you need to add html elements to the body, you need to wait for it to load, so you need to execute these codes in window.onload().
A simple example:
window.onload = function() {
const template = document.createElement('template');
template.innerHTML = "<h3>This is a template.</h3>";
document.body.appendChild(template);
}
function show() {
var shadowEle = document.getElementById('shadow1');
var shadow = shadowEle.attachShadow({
mode: 'open'
});
//add style
const styles = document.createElement("style");
styles.textContent = 'h3{ color:blue; }';
shadow.appendChild(styles);
//add h3 title
const title = document.createElement("h3");
var temp = document.getElementsByTagName("template")[0];
var clon = temp.content.cloneNode(true);
title.textContent = clon.childNodes[0].textContent;
shadow.appendChild(title);
}
if (!window.customElements) {
document.write('Web components not supported');
alert('hi');
console.log('No web component');
} else {
alert('Web componnets is supported');
}
<script src="https://unpkg.com/#webcomponents/webcomponentsjs#2.5.0/webcomponents-loader.js"></script>
<input type="button" name="show" id="show" value="show template" onclick="show()" />
<br />
<my-shadow id="shadow1">
</my-shadow>
Result in IE:

How to access stylesheet when I render as EJS in a dynamic route based on URL parameters

I recently started learning Express.js and mongoose by creating a simple To-Do List, and now I'm stuck with a basic thing.
When I tried to render a EJS file in a dynamic route based on URL parameters, I was unable to apply the stylesheet.css which is stored in the public folder for the EJS, and the following error message showed up in the browser console.
The error message:
Refused to apply style from 'http://localhost:3000/mylist/css/styles.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
And of course, the URL request was http://localhost:3000/mylist/css/styles.css.
On the other hand, the same EJS file rendered in the home route was able to access the stylesheet.css. And the request URL was http://localhost:3000/css/styles.css.
So I guess whatever I try to render, the request URL has to be http://localhost:3000/css/styles.css in my file structure. However, I don't know how to get rid of mylist from http://localhost:3000/mylist/css/styles.css, when I render as EJS in a dynamic route based on URL parameters.
What's wrong with my code? How do you solve it?
File structure:
app.js
const express = require("express");
const mongoose = require("mongoose");
const app = express();
app.set('view engine', 'ejs');
app.use(express.static("/public"));
mongoose.connect("mongodb://localhost:27017/todolistDB", {useNewUrlParser: true, useUnifiedTopology: true});
const itemsSchema = mongoose.Schema({ name: String });
const customListsSchema = mongoose.Schema({ name: String, items: [itemsSchema] });
const Item = mongoose.model("Item", itemsSchema);
const CustomList = mongoose.model("CustomList", customListsSchema);
app.get("/", (req, res) => {
Item.find((err, foundItems) => {
if (!err) {
res.render("list", { listTitle: "Today", newListItems: foundItems });
}
});
});
app.get("/mylist/:customListName", (req, res) => {
const customListName = req.params.customListName;
CustomList.findOne({ name: customListName }, (err, foundCustomList) => {
if (!err) {
if (foundCustomList) {
res.render("list", {
listTitle: foundCustomList.name,
newListItems: foundCustomList.items
});
} else {
const customList = new CustomList({ name: customListNamem });
customList.save((err) => {
if (!err) {
res.redirect("/mylist/" + customListName);
}
});
}
}
});
});
list.ejs:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>To-Do List</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div class="box" id="heading">
<h1> <%= listTitle %> </h1>
</div>
<div class="box">
<form action="/delete" method="post">
<% newListItems.forEach((newListItem) => { %>
<div class="item">
<input type="checkbox" name="checkbox" value="<%= newListItem._id %>" onchange="this.form.submit()">
<p><%= newListItem.name %></p>
</div>
<% }); %>
</form>
<form class="item" action="/" method="post">
<input type="text" name="newItem" placeholder="New Item" autocomplete="off">
<button type="submit" name="listName" value="<%= listTitle %>">+</button>
</form>
</div>
</body>
</html>
I also tried and replaced app.use(express.static("/public")); with app.use(express.static(__dirname + "/public"));, but it didn't work either.
I figured out how to fix the bug by myself.
It was caused by the fact that the path to the stylesheet in the EJS was a relative path. It seems that it has to be an absolute path.
So I simply added a forward slash / and turned <link rel="stylesheet" href="css/styles.css"> to <link rel="stylesheet" href="/css/styles.css"> in the list.ejs.
Now it works with app.use(express.static(__dirname + "/public")); in app.js, however it doesn't still work with app.use(express.static("/public"));.
What's the difference between them?
I couple __dirname with path.join() to create an absolute path to the directory, that will work on the machine it is being run on.
What you're doing with:
app.use(express.static(path.join(__dirname, 'public'))); setting the static folder to /Absolute/Path/To/Repo/public/ where the __dirname is the "/Absolute/Path/To/Repo/" part.
So run on my computer it would return:
/home/proto/UWebDev/Playground/myapp/public
and on your it would be whatever the path to that particular public directory would be.
Conversely, in your initial code you simply used:
app.use(express.static("/public") ; which if console logged anywhere will return /public. Which is an absolute path that I'm assuming isn't in you repo since,I haven't heard of anyone making a repo out of there machine's root directory.
For what it's worth, what you've done in app.js made it so anything on the front-end that begins with / is actually referencing /ABS/PATH/TO/REPO/public/; in this particular scenario, I believe simply replacing your original "/public" with "./public" would have worked as well (in addition adding the / to the path to the css as you did.
Sidenote: path.join(__dirname,'public) === path.join(__dirname,'/public') for what it's worth.
I'm fairly new to developing, so if anyone sees something I made a mistake on or oversimplified, please point it out, but hopefully this helps someone who get to this page looking for answers.