SSR with React : Unexpected token '<' in call to renderToString() - javascript

I'm working on SSR with react but I'm encountering the following error.
Syntax error: Unexpected token '<'`
<div id="root">${ReactDOMServer.renderToString(<App />)}</div>```
^
As mentioned is here
babel-register doesn't process the file it is called from.
Therefore, I rightly declared my babel dependencies in a new file, however I'm still getting the above error.
Below is my index.js file
import babelRegister from '#babel/register';
import ignoreStyles from 'ignore-styles';
babelRegister({
ignore: [/node_modules/],
presets: ['#babel/preset-env', '#babel/preset-react'],
});
import express from 'express';
import appRender from './server.js';
const app = express();
appRender(app);
My server.js file.
import initialRenderRoutes from './routes/initialRenderRoutes.js';
import path from 'path';
const appRender = (app) => {
const __dirname = path.resolve();
app.use(express.static(path.resolve(__dirname, '../build')));
app.use('*', initialRenderRoutes);
const port = 5000;
app.listen(port, () => console.log(`Server running on port ${port}`));
};
export default appRender;
My initialController.js file
import fs from 'fs';
import ReactDOMServer from 'react-dom/server.js';
import path from 'path';
import App from '../../src/App.js';
const initialRenderController = (req, res, next) => {
console.log(path.resolve());
fs.readFile(
path.resolve('../client/build/index.html'),
'utf8',
(err, data) => {
if (err) {
console.log(err);
return res.status(500).send('Internal Server Error');
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
<<<<The problem lies here>>>>
)
);
}
);
};
export default initialRenderController;
Is it something related to babel, please help.

Try the below changes in your index.js file,
require('ignore-styles');
require('#babel/register')({
ignore: [/(node_modules)/],
presets: ['#babel/preset-env', '#babel/preset-react']
});
require('./server');
require('./initialController');
The above should work, I tested locally the below, it works perfectly fine.
My server.js
import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from '../App';
const app = express();
app.use('^/$', (req, res, next) => {
fs.readFile(path.resolve('./build/index.html'), 'utf-8', (err, data) => {
if (err) {
console.log(err);
return res.status(500).send("Some error occurred")
}
return res.send(data.replace('<div id="root"></div>', `<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`))
})
});
app.use(express.static(path.resolve(__dirname, "..", "build")));
app.listen(5000, ()=>{
console.log("App running on port 5k")
})
index.js
require('ignore-styles');
require('#babel/register')({
ignore: [/(node_modules)/],
presets: ['#babel/preset-env', '#babel/preset-react']
});
require('./server');
I hope you have the .babelrc file with the required presets.
Update in response to comment:
Consider removing type: "module", since it will throw error when you use require. #babel/register will run files using babel on the fly. The require hook will bind itself to the node’s require and will automatically compile files at runtime. server.js using es module won't clash if you remove type: "module". The order of require matters, we require babel-register in index.js with the presets needed to recognize the syntaxes in the then-required server.js.

I believe there are two things that need to be changed. One on your initialControler.js you are using export default in a node.js file, use module.exports
module.exports vs. export default in Node.js and ES6
You should change all the imports in your node files.
You use export / export default in React and then import to pull in the files
https://www.geeksforgeeks.org/reactjs-importing-exporting/
module.exports and require to pull in the files for Node
What is the purpose of Node.js module.exports and how do you use it?
Second they moved the app.get into that renderReact.js file and then required it into their index.js file. However on your server.js file I don't see you importing in your initialController file.
From your example it looks like you should be doing something like this:
Server.js
let initialController = require('/path to this file');
initialController(app)

Yow broh don’t waste yow time reading them parchments.
All you need to do is remove any space b4 and after each ><
const val= ReactDOMServer.renderToString(<App />);
/// make sure you don’t have any sort of space
/// between them > < and yow ${}
/// is better if you store that long text into an small var, as I did to ///prevent prettier or some other 💩 to add a line break of an space
return res.send( `<div id="root">${val}</div>`);

Related

vitest Router.use() requires a middleware function but got a undefined

I am trying to unit test my router object in Express but inside the unit test file the object returns undefined
Here is a minimal version of my app
src/config/apiVersion.js
// update major versions here
const version = '/v2'
export default version
src/routes/index.js
import express from 'express'
import {
healthRouter,
healthUrl
} from './health/index.js'
const router = express.Router()
// add new routes here
const allRoutes = [
{
path: healthUrl,
route: healthRouter
}
]
// tell the router to use the routes you added
allRoutes.forEach((route) => {
router.use(route.path, route.route)
})
export default router
src/routes/health/index.js
import express from 'express'
import { healthController } from '../../controllers/health/index.js'
const healthRouter = express.Router()
const healthUrl = '/health'
healthRouter.route('/')
.get(healthController)
export {
healthRouter,
healthUrl
}
src/app.js (note I omitted most of the app.use's such as app.us(cors()) for example
// version is just the string '/v2'
import version from './config/apiVersion.js'
import router from './routes/index.js'
const app = express()
// some other app.use's here omitted like app.use(cors)
// add routes
app.use(`${version}`, router)
// custom 404 to handle non-existent paths/typos on paths
app.use((req, res) => {
res.status(404).send({ error: 'Path does not exist, check for typos. If querying /soap you also need vendor and method in the path' })
})
// custom error handler
app.use((err, req, res) => {
appLogger.error('There was an error: ' + err.stack)
res.status(500).send('Something broke!')
})
export default app
Here is my test file
import router from '../../../src/routes/index.js'
// to make sure the number of routes doesn't change without a new test added
const actualNumberRoutes = 2
describe('router', () => {
it('should return all the routes', () => {
let numberOfRoutes = 0
router.stack.forEach((layer) => {
expect(layer.name).toEqual('router')
numberOfRoutes += 1
})
expect(numberOfRoutes).toEqual(actualNumberRoutes)
})
})
And the error for this file where router is coming up as undefined
Try to provide your app to use routes after importing your respective route files like this.
import healthRouter from 'src/routes/health/index.js';
import router from '../../../src/routes/index.js';
const app=express();
app.use("your_path",router);
app.use("your_health_path",healthRouter);

How to store files in production in Next.js?

I have a file exchange Next.js app that I would like to deploy. In development whenever file is dropped, the app stores the file in root public folder, and when the file is downloaded the app takes it from there as well using the <a> tag with href attribute of uploads/{filename}. This all works pretty well in development, but not in production.
I know that whenever npm run build is run, Next.js takes the files from public folder and the files added there at runtime will not be served.
The question is are there any ways of persistent file storage in Next.js apart from third party services like AWS S3?
Next.js does allow file storage at buildtime, but not at runtime. Next.js will not be able to fulfill your file upload requirement. AWS S3 is the best option here.
next in node runtime is NodeJS, thus If your cloud provider allows create persistent disk and mount it to your project, then you can do it:
e.g. pages/api/saveFile.ts:
import { writeFileSync } from 'fs';
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { path = null } = req.query;
if (!path) {
res.status(400).json({ name: 'no path provided' })
} else {
// your file content here
const content = Date.now().toString();
writeFileSync(`/tmp/${path}.txt`, content);
res.json({
path,
content
})
}
}
/tmp works almost in every cloud provider (including Vercel itself), but those files will be lost on next deployment; instead you should use your mounted disk path
pages/api/readFile.ts
import { readFileSync } from 'fs';
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { path = '' } = req.query;
if (!path) {
res.status(400).json({ name: 'wrong' })
} else {
res.send(readFileSync(`/tmp/${path}`));
}
}
Live running example:
fetch('https://eaglesdoc.vercel.app/api/writefile?path=test')
.then(res => res.text()).then(res => console.log(res)).then( () =>
fetch('https://eaglesdoc.vercel.app/api/readfile?path=test'))
.then(res => res.text()).then(res => console.log(res))

Cross import in node.js

I´m making a node.js project in typescript. I want to put my socket.io code in a separate file to organize.
I see in the console that socketHandling.ts is loaded before index.ts, which I find odd.
But the problem is that Typeof server in socketHandling.ts is undefined.
How do I make sure the server variable from index.ts is defined before socketHandling.ts is executed?
index.ts
import * as express from "express";
import * as path from "path";
import * as socketHandle from "./socketHandling";
console.log("index.ts loaded");
export const server = express()
.use(express.static(path.join(__dirname, "../../public")))
.set("views", path.join(__dirname, "../../views"))
.set("view engine", "ejs")
.get("/*", httpGet)
.post("/*", httpPost)
.listen(PORT, () => console.log(`Listening on ${PORT}`));
socketHandle.initSockets();
socketHandling.ts
import { Server } from "socket.io";
import { server } from "./index";
console.log("socketHandling.ts loaded");
console.log(server);
const io = new Server(server);
export function initSockets(): void {
io.on("connection", (socket) => {
console.log(`Client connected ${socket.id}`);
socket.on("disconnect", () => {
console.log(`Client disconnected ${socket.id}`);
});
});
}
Why does it go through socketHandling before the index server is defined?
If index.ts is your entry point, once the program counter reaches line 3 of index.ts, the import will make it start executing socketHandling.ts, which contains another import back to index.ts, which wasn't even done executing yet. So this looks like a circular import maybe you should avoid the situation altogether.
Proposed Solution:
Avoid the circular import by passing in the server from the top into the initSockets method that you import from the library file.
Try the following refactor:
index.ts
import * as express from "express";
import * as path from "path";
import * as socketHandle from "./socketHandling";
console.log("index.ts loaded");
export const server = express()
.use(express.static(path.join(__dirname, "../../public")))
.set("views", path.join(__dirname, "../../views"))
.set("view engine", "ejs")
.get("/*", httpGet)
.post("/*", httpPost)
.listen(PORT, () => console.log(`Listening on ${PORT}`));
socketHandle.initSockets(server);
socketHandling.ts
import { Server } from "socket.io";
console.log("socketHandling.ts loaded");
let io: Server;
export function initSockets(server): void {
if (!io) {
io = new Server(server);
}
io.on("connection", (socket) => {
console.log(`Client connected ${socket.id}`);
socket.on("disconnect", () => {
console.log(`Client disconnected ${socket.id}`);
});
});
}
Typically it wont be sustainable to export stuff from your application entry point for the entire library to use. Usually you see these kinds of variables passed down into methods rather than globally referenced across different files throughout the entire application.

env-vars in React using Dotenv and Webpack

I want to access some of my environment variables defined in the frontend (React).
I have the following setup:
React + NodeJS (I do NOT use create-react-app)
Webpack 4
Dotenv
I have tried to follow https://medium.com/#trekinbami/using-environment-variables-in-react-6b0a99d83cf5#0618 but it does not work either any error is thrown.
webpack.config.js
const dotenv = require("dotenv");
module.exports = () => {
// call dotenv and it will return an Object with a parsed key
const env = dotenv.config().parsed;
// reduce it to a nice object, the same as before
const envKeys = Object.keys(env).reduce((prev, next) => {
console.log(prev)
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
return {
...,
plugins: [
new webpack.DefinePlugin(envKeys)
],
...
}
With above Webpack config I think I should be able to do <h4>My var: {process.env.REACT_APP_MY_VAR}</h4> in file.js, of course I have defined REACT_APP_MY_VAR in my .env-file located in the project root.
With above I expect file.js to render the value of REACT_APP_MY_VAR, but i does render nothing, either the value or an error.
I would recommend using dotenv-webpack instead of dotenv package for easy configuration.
4 simple steps:-
1) install dotenv-wepack using
npm install dotenv-webpack --save
2) Create .env file at root of application
API_URL=http://localhost:8000
3) Add this to your webpack config file.
const Dotenv = require('dotenv-webpack');
module.exports = {
...
plugins: [
new Dotenv()
]
...
};
4) Use env variable inside your application anywhere.
import React from 'react';
const App = () => {
return (
<h1>{process.env.API_URL}</h1>
);
}
export default App;
Hope that helps!!!

how to redirect all server requests to a function in Firebase Hosting

Trying to implement SSR with Firebase so I'm using a function to prerender each page of a React App. It's working well except the home page, so it must be either the match is wrong on the firebase redirect or possibly on the express route itself.
firebase.json
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint"
]
},
"hosting": {
"public": "build",
"rewrites": [
{
"source": "**",
"function": "contentServer"
}
],
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
contentServer.js
import * as functions from 'firebase-functions';
import * as fs from 'fs';
import * as path from 'path';
import React from 'react';
import Helmet from 'react-helmet';
import { renderToString } from 'react-dom/server';
import Server from '../browser/Server.js';
const express = require('express');
const app = express();
// might be this? Also tried /**
app.get(['**'], (request, response) => {
const context = {};
const location = request.url;
console.log('Processing request for ', location);
let appCode;
try {
appCode = renderToString(<Server context={context} location={location} />);
} catch (err) {
appCode = 'with error';
}
// const appState = {
// pageTitle: 'Hello World',
// };
// const preloadedState = JSON.stringify(appState).replace(/</g, '\\u003c');
const fileName = path.join(__dirname, '../index.html');
const htmlTemplate = fs.readFileSync(fileName, 'utf8');
const head = Helmet.renderStatic();
const responseString = htmlTemplate
.replace('<div id="root"></div>', `<div id="root">${appCode}</div>`)
.replace('<title>React App</title>', `${head.title}\n${head.link}`);
return response.send(responseString);
});
export default functions.https.onRequest(app);
Curl
I run firebase serve --only functions,hosting
Then use curl to check the response:
curl http://localhost:5000 - does not render the home page - just the standard react page
curl http://localhost:5000/ - also does not work - just the standard react page.
curl http://localhost:5000/contact-us - works well and returns the contact us page, all other pages on the site work and trigger the function.
If you want redirect every single URL to your host to an express app in Cloud Functions, you will need to do the following:
Make sure there is no index.html in your public hosting folder (otherwise it will always be served with the path /).
Configure Firebase hosting in firebase.json to rewrite all urls to a function (you are currently doing this in your "hosting" block, which is good):
"rewrites": [
{
"source": "**",
"function": "contentServer"
}
]
Write a Cloud Function exported with the same name as the function in the rewrite, and attach an express app that handles the route wildcarded with *. In index.js in your functions folder, minimally:
const functions = require('firebase-functions')
const express = require('express')
const app = express()
app.get("*", (request, response) => {
response.send("OK")
})
exports.contentServer = functions.https.onRequest(app)
If you run this locally with firebase serve --only hosting,functions, every path that you send to localhost:5000 will say "OK".

Categories