mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support server render html (#5471)
* feat: node server * fix: replace node to bun
This commit is contained in:
parent
f6e33a59d5
commit
52e7fb49cd
2
.github/workflows/deploy_test_web.yaml
vendored
2
.github/workflows/deploy_test_web.yaml
vendored
@ -62,7 +62,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
SSH_PRIVATE_KEY: ${{ env.SSH_PRIVATE_KEY }}
|
SSH_PRIVATE_KEY: ${{ env.SSH_PRIVATE_KEY }}
|
||||||
ARGS: "-rlgoDzvc -i"
|
ARGS: "-rlgoDzvc -i"
|
||||||
SOURCE: "frontend/appflowy_web_app/dist frontend/appflowy_web_app/Dockerfile frontend/appflowy_web_app/nginx.conf frontend/appflowy_web_app/.env nginx-signed.crt nginx-signed.key"
|
SOURCE: "frontend/appflowy_web_app/dist frontend/appflowy_web_app/server.cjs frontend/appflowy_web_app/start.sh frontend/appflowy_web_app/Dockerfile frontend/appflowy_web_app/nginx.conf frontend/appflowy_web_app/.env nginx-signed.crt nginx-signed.key"
|
||||||
REMOTE_HOST: ${{ env.REMOTE_HOST }}
|
REMOTE_HOST: ${{ env.REMOTE_HOST }}
|
||||||
REMOTE_USER: ${{ env.REMOTE_USER }}
|
REMOTE_USER: ${{ env.REMOTE_USER }}
|
||||||
EXCLUDE: "frontend/appflowy_web_app/dist/, frontend/appflowy_web_app/node_modules/"
|
EXCLUDE: "frontend/appflowy_web_app/dist/, frontend/appflowy_web_app/node_modules/"
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
FROM node:latest
|
FROM oven/bun:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y nginx
|
apt-get install -y nginx
|
||||||
|
|
||||||
|
RUN bun install cheerio pino axios pino-pretty
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
RUN addgroup --system nginx && \
|
RUN addgroup --system nginx && \
|
||||||
adduser --system --no-create-home --disabled-login --ingroup nginx nginx
|
adduser --system --no-create-home --disabled-login --ingroup nginx nginx
|
||||||
|
|
||||||
@ -18,6 +24,11 @@ COPY nginx-signed.key /etc/ssl/private/nginx-signed.key
|
|||||||
|
|
||||||
RUN chown -R nginx:nginx /etc/ssl/certs/nginx-signed.crt /etc/ssl/private/nginx-signed.key
|
RUN chown -R nginx:nginx /etc/ssl/certs/nginx-signed.crt /etc/ssl/private/nginx-signed.key
|
||||||
|
|
||||||
|
COPY start.sh /app/start.sh
|
||||||
|
|
||||||
|
RUN chmod +x /app/start.sh
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["/app/start.sh"]
|
||||||
|
@ -4,8 +4,28 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/appflowy.svg" />
|
<link rel="icon" type="image/svg+xml" href="/appflowy.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
<title>AppFlowy</title>
|
<title>AppFlowy</title>
|
||||||
|
<meta name="description"
|
||||||
|
content="AppFlowy is an AI collaborative workspace where you achieve more without losing control of your data"
|
||||||
|
/>
|
||||||
|
<meta property="og:title" content="AppFlowy" />
|
||||||
|
<meta property="og:description"
|
||||||
|
content="AppFlowy is an AI collaborative workspace where you achieve more without losing control of your data"
|
||||||
|
/>
|
||||||
|
<meta property="og:image"
|
||||||
|
content="https://d3uafhn8yrvdfn.cloudfront.net/website/production/_next/static/media/og-image.e347bfb5.png"
|
||||||
|
/>
|
||||||
|
<meta property="og:url" content="https://appflowy.com" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="AppFlowy" />
|
||||||
|
<meta name="twitter:description"
|
||||||
|
content="AppFlowy is an AI collaborative workspace where you achieve more without losing control of your data"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:image"
|
||||||
|
content="https://d3uafhn8yrvdfn.cloudfront.net/website/production/_next/static/media/og-image.e347bfb5.png"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:site" content="@appflowy" />
|
||||||
|
<meta name="twitter:creator" content="@appflowy" />
|
||||||
</head>
|
</head>
|
||||||
<body id="body">
|
<body id="body">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -30,6 +30,10 @@ http {
|
|||||||
|
|
||||||
gzip_http_version 1.0;
|
gzip_http_version 1.0;
|
||||||
|
|
||||||
|
gzip_comp_level 5;
|
||||||
|
|
||||||
|
gzip_vary on;
|
||||||
|
|
||||||
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript application/wasm;
|
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript application/wasm;
|
||||||
|
|
||||||
# Existing server block for HTTP
|
# Existing server block for HTTP
|
||||||
@ -61,15 +65,22 @@ http {
|
|||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/html;
|
proxy_pass http://localhost:3000;
|
||||||
index index.html index.htm;
|
proxy_http_version 1.1;
|
||||||
try_files $uri $uri/ /index.html;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /static/ {
|
location /static/ {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
expires 30d;
|
expires 30d;
|
||||||
access_log off;
|
access_log off;
|
||||||
|
location ~* \.wasm$ {
|
||||||
|
types { application/wasm wasm; }
|
||||||
|
default_type application/wasm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
location /appflowy.svg {
|
location /appflowy.svg {
|
||||||
|
@ -8862,7 +8862,7 @@ packages:
|
|||||||
/randombytes@2.1.0:
|
/randombytes@2.1.0:
|
||||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.1.2
|
safe-buffer: 5.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/react-beautiful-dnd@13.1.1(react-dom@18.2.0)(react@18.2.0):
|
/react-beautiful-dnd@13.1.1(react-dom@18.2.0)(react@18.2.0):
|
||||||
@ -9604,6 +9604,10 @@ packages:
|
|||||||
/safe-buffer@5.1.2:
|
/safe-buffer@5.1.2:
|
||||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||||
|
|
||||||
|
/safe-buffer@5.2.1:
|
||||||
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/safe-regex-test@1.0.3:
|
/safe-regex-test@1.0.3:
|
||||||
resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
|
resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
114
frontend/appflowy_web_app/server.cjs
Normal file
114
frontend/appflowy_web_app/server.cjs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const pino = require('pino');
|
||||||
|
const cheerio = require('cheerio');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const distDir = path.join(__dirname, 'dist');
|
||||||
|
const indexPath = path.join(distDir, 'index.html');
|
||||||
|
|
||||||
|
const setOrUpdateMetaTag = ($, selector, attribute, content) => {
|
||||||
|
if ($(selector).length === 0) {
|
||||||
|
$('head').append(`<meta ${attribute}="${selector.match(/\[(.*?)\]/)[1]}" content="${content}">`);
|
||||||
|
} else {
|
||||||
|
$(selector).attr('content', content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Create a new logger instance
|
||||||
|
const logger = pino({
|
||||||
|
transport: {
|
||||||
|
target: 'pino-pretty',
|
||||||
|
level: 'info',
|
||||||
|
options: {
|
||||||
|
colorize: true,
|
||||||
|
translateTime: 'SYS:standard',
|
||||||
|
destination: `${__dirname}/pino-logger.log`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const logRequestTimer = (req) => {
|
||||||
|
const start = Date.now();
|
||||||
|
const pathname = new URL(req.url).pathname;
|
||||||
|
logger.info(`Incoming request: ${pathname}`);
|
||||||
|
return () => {
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
logger.info(`Request for ${pathname} took ${duration}ms`);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMetaData = async (url) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error fetching meta data', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createServer = async (req) => {
|
||||||
|
const timer = logRequestTimer(req);
|
||||||
|
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
const pageId = req.url.split('/').pop();
|
||||||
|
let htmlData = fs.readFileSync(indexPath, 'utf8');
|
||||||
|
const $ = cheerio.load(htmlData);
|
||||||
|
if (!pageId) {
|
||||||
|
timer();
|
||||||
|
return new Response($.html(), {
|
||||||
|
headers: { 'Content-Type': 'text/html' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const description = 'Write, share, comment, react, and publish docs quickly and securely on AppFlowy.';
|
||||||
|
let title = 'AppFlowy';
|
||||||
|
const url = 'https://appflowy.com';
|
||||||
|
let image = 'https://d3uafhn8yrvdfn.cloudfront.net/website/production/_next/static/media/og-image.e347bfb5.png';
|
||||||
|
// Inject meta data into the HTML to support SEO and social sharing
|
||||||
|
// if (metaData) {
|
||||||
|
// title = metaData.title;
|
||||||
|
// image = metaData.image;
|
||||||
|
// }
|
||||||
|
|
||||||
|
$('title').text(title);
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="description"]', 'name', description);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:title"]', 'property', title);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:description"]', 'property', description);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:image"]', 'property', image);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:url"]', 'property', url);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:type"]', 'property', 'article');
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:card"]', 'name', 'summary_large_image');
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:title"]', 'name', title);
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:description"]', 'name', description);
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:image"]', 'name', image);
|
||||||
|
|
||||||
|
timer();
|
||||||
|
return new Response($.html(), {
|
||||||
|
headers: { 'Content-Type': 'text/html' },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
timer();
|
||||||
|
logger.error({ message: 'Method not allowed', method: req.method });
|
||||||
|
return new Response('Method not allowed', { status: 405 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const start = () => {
|
||||||
|
try {
|
||||||
|
Bun.serve({
|
||||||
|
port: 3000,
|
||||||
|
fetch: createServer,
|
||||||
|
error: (err) => {
|
||||||
|
logger.error(`Internal Server Error: ${err}`);
|
||||||
|
return new Response('Internal Server Error', { status: 500 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logger.info(`Server is running on port 3000`);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start();
|
@ -30,6 +30,7 @@ function CollaborativeEditor({ doc }: { doc: Y.Doc }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
|
|
||||||
editor.connect();
|
editor.connect();
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
|
|
||||||
|
10
frontend/appflowy_web_app/start.sh
Normal file
10
frontend/appflowy_web_app/start.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Start the frontend server
|
||||||
|
bun run server.cjs &
|
||||||
|
|
||||||
|
# Start the nginx server
|
||||||
|
service nginx start
|
||||||
|
|
||||||
|
tail -f /dev/null
|
||||||
|
|
Loading…
Reference in New Issue
Block a user