Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 1 addition & 31 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,6 @@
<title>성장하는 당신을 위한 회고 서비스, Layer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />

<!-- Google tag -->
<script async src="https://www.googletagmanager.com/gtag/js?id=%VITE_GOOGLE_ANALYTICS%"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());

gtag("config", "%VITE_GOOGLE_ANALYTICS%");
</script>

<script
async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9261746317470679"
Expand Down Expand Up @@ -58,24 +46,7 @@
<meta property="og:image" content="https://kr.object.ncloudstorage.com/layer-bucket/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="layer" />
<meta property="og:locale" content="ko_KR" />

<!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="성장하는 당신을 위한 회고 서비스, Layer" />
<meta name="twitter:description" content="편리한 회고 작성부터 AI 분석까지 Layer에서 함께해요!" />
<meta name="twitter:image" content="https://kr.object.ncloudstorage.com/layer-bucket/og-image.png" />

<!-- Open Graph Meta Tags -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://www.layerapp.io/" />
<meta property="og:title" content="성장하는 당신을 위한 회고 서비스, Layer" />
<meta property="og:description" content="편리한 회고 작성부터 AI 분석까지 Layer에서 함께해요!" />
<meta property="og:image" content="https://kr.object.ncloudstorage.com/layer-bucket/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="layer" />
<meta property="og:site_name" content="Layer" />
<meta property="og:locale" content="ko_KR" />

<!-- Twitter Card Meta Tags -->
Expand All @@ -84,7 +55,6 @@
<meta name="twitter:description" content="편리한 회고 작성부터 AI 분석까지 Layer에서 함께해요!" />
<meta name="twitter:image" content="https://kr.object.ncloudstorage.com/layer-bucket/og-image.png" />

<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
<script src="https://developers.kakao.com/sdk/js/kakao.min.js"></script>
<script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
</head>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
User-agent: *
Allow: /
sitemap: https://layerapp.io/sitemap.xml

Sitemap: https://www.layerapp.io/sitemap.xml
77 changes: 64 additions & 13 deletions apps/web/server/server.cjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require('dotenv').config();
require("dotenv").config();
const express = require("express");
const fs = require("fs");
const path = require("path");
const cheerio = require("cheerio");
const axios = require("axios");
const CryptoJS = require("crypto-js"); // crypto-js를 추가합니다.
const CryptoJS = require("crypto-js"); // crypto-js를 추가합니다.

const app = express();

Expand Down Expand Up @@ -69,14 +69,14 @@ app.get("/space/join/:id", async (req, res) => {
try {
const $ = cheerio.load(html);

$('title').text(`${leaderName}님의 회고 초대장`);
$('meta[name="description"]').attr('content', `함께 회고해요! ${leaderName}님이 ${teamName} 스페이스에 초대했어요.`);
$('meta[property="og:title"]').attr('content', `${leaderName}님의 회고 초대장`);
$('meta[property="og:description"]').attr('content', `함께 회고해요! ${leaderName}님이 ${teamName} 스페이스에 초대했어요.`);
$('meta[property="og:image"]').attr('content', 'https://kr.object.ncloudstorage.com/layer-bucket/retrospectOG.png');
$('meta[name="twitter:title"]').attr('content', `${leaderName}님의 회고 초대장`);
$('meta[name="twitter:description"]').attr('content', `함께 회고해요! ${leaderName}님이 ${teamName} 스페이스에 초대했어요.`);
$('meta[name="twitter:image"]').attr('content', 'https://kr.object.ncloudstorage.com/layer-bucket/retrospectOG.png');
$("title").text(`${leaderName}님의 회고 초대장`);
$('meta[name="description"]').attr("content", `함께 회고해요! ${leaderName}님이 ${teamName} 스페이스에 초대했어요.`);
$('meta[property="og:title"]').attr("content", `${leaderName}님의 회고 초대장`);
$('meta[property="og:description"]').attr("content", `함께 회고해요! ${leaderName}님이 ${teamName} 스페이스에 초대했어요.`);
$('meta[property="og:image"]').attr("content", "https://kr.object.ncloudstorage.com/layer-bucket/retrospectOG.png");
$('meta[name="twitter:title"]').attr("content", `${leaderName}님의 회고 초대장`);
$('meta[name="twitter:description"]').attr("content", `함께 회고해요! ${leaderName}님이 ${teamName} 스페이스에 초대했어요.`);
$('meta[name="twitter:image"]').attr("content", "https://kr.object.ncloudstorage.com/layer-bucket/retrospectOG.png");

res.send($.html());
} catch (err) {
Expand All @@ -85,11 +85,62 @@ app.get("/space/join/:id", async (req, res) => {
}
});

const BASE_URL = "https://www.layerapp.io";

// 색인 허용 라우트
const PUBLIC_ROUTES = ["/", "/login"];

// 존재하지만 색인하지 않는 라우트 prefix. src/router/index.tsx에 최상위 경로를 추가하면 여기도 추가해야 404가 되지 않는다.
const NOINDEX_ROUTE_PREFIXES = [
"/desktop",
"/myinfo",
"/space",
"/retrospect",
"/write",
"/goals",
"/analysis",
"/setnickname",
"/template",
"/staging",
"/api/auth/oauth2",
];

function resolveRoutePolicy(pathname) {
const routePath = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;

if (PUBLIC_ROUTES.includes(routePath)) {
return { status: 200, noindex: false, canonical: `${BASE_URL}${routePath}` };
}
if (NOINDEX_ROUTE_PREFIXES.some((prefix) => routePath === prefix || routePath.startsWith(`${prefix}/`))) {
return { status: 200, noindex: true };
}
return { status: 404, noindex: true };
}

app.get("*", (req, res) => {
const filePath = path.join(distPath, "index.html");
res.sendFile(filePath);
let html;
try {
html = fs.readFileSync(path.join(distPath, "index.html"), "utf8");
} catch (err) {
console.error("Failed to read index.html:", err);
return res.status(500).send("Error loading the page.");
}

const { status, noindex, canonical } = resolveRoutePolicy(req.path);
const $ = cheerio.load(html);

if (noindex) {
$('meta[name="robots"]').attr("content", "noindex, nofollow");
$('link[rel="canonical"]').remove();
} else {
$('link[rel="canonical"]').attr("href", canonical);
$('meta[property="og:url"]').attr("content", canonical);
}

res.set("Cache-Control", "public, max-age=0, s-maxage=60");
res.status(status).send($.html());
});

app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
});
6 changes: 1 addition & 5 deletions apps/web/vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@
],
"routes": [
{ "handle": "filesystem" },
{
"src": "/space/join/(.*)",
"dest": "/server/server.cjs"
},
{
"src": "/.*",
"dest": "/index.html"
"dest": "/server/server.cjs"
}
]
}
6 changes: 5 additions & 1 deletion apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export default defineConfig(() => ({
},
}),
svgr(),
Sitemap({ hostname: "https://layerapp.io" }),
Sitemap({
hostname: "https://www.layerapp.io",
// 기본값(true)이면 public/robots.txt를 dist에서 덮어쓰므로 robots.txt는 public에서 직접 관리
generateRobotsTxt: false,
}),
VitePluginRadar({
analytics: process.env.VITE_GOOGLE_ANALYTICS ? { id: process.env.VITE_GOOGLE_ANALYTICS } : undefined,
}),
Expand Down
Loading