fix: SSR 데이터 주입 방식 수정 - layout에서 ssrData script 태그 직접 출력
- app.js: initScript() 헬퍼로 <script>var __INIT__=데이터</script> 생성 - layout.ejs: <%- ssrData %> 로 body 뒤에 script 태그 삽입 - 모든 페이지에서 첫 로드 시 즉시 데이터 표시 확인 완료 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-19
@@ -97,44 +97,42 @@ app.get('/logout', (req, res) => {
|
|||||||
// ===== 루트 → 관리자로 리다이렉트 =====
|
// ===== 루트 → 관리자로 리다이렉트 =====
|
||||||
app.get('/', (req, res) => res.redirect('/admin'));
|
app.get('/', (req, res) => res.redirect('/admin'));
|
||||||
|
|
||||||
|
// 서버사이드 데이터를 script 태그로 만드는 헬퍼
|
||||||
|
function initScript(data) {
|
||||||
|
return '<script>var __INIT__=' + JSON.stringify(data).replace(/</g, '\\u003c') + ';</script>';
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 관리자 페이지 (데이터 서버사이드 렌더링) =====
|
// ===== 관리자 페이지 (데이터 서버사이드 렌더링) =====
|
||||||
app.get('/admin', adminAuth, async (req, res) => {
|
app.get('/admin', adminAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const sites = await db.query(`SELECT s.*, (SELECT COUNT(*) FROM crawl_results WHERE site_id = s.id) AS crawl_count FROM sites s ORDER BY s.id`);
|
const sites = await db.query(`SELECT s.*, (SELECT COUNT(*) FROM crawl_results WHERE site_id = s.id) AS crawl_count FROM sites s ORDER BY s.id`);
|
||||||
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
|
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
|
||||||
const logs = await db.query(`SELECT l.*, s.name AS site_name FROM crawl_logs l LEFT JOIN sites s ON s.id = l.site_id ORDER BY l.created_at DESC LIMIT 10`);
|
const logs = await db.query(`SELECT l.*, s.name AS site_name FROM crawl_logs l LEFT JOIN sites s ON s.id = l.site_id ORDER BY l.created_at DESC LIMIT 10`);
|
||||||
res.render('admin/dashboard', {
|
res.render('admin/dashboard', { ssrData: initScript({ sites: sites.rows, adsense: adsense.rows, logs: logs.rows }) });
|
||||||
initialData: JSON.stringify({ sites: sites.rows, adsense: adsense.rows, logs: logs.rows })
|
} catch (e) { res.render('admin/dashboard', { ssrData: initScript({}) }); }
|
||||||
});
|
|
||||||
} catch (e) { res.render('admin/dashboard', { initialData: '{}' }); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/sites', adminAuth, async (req, res) => {
|
app.get('/admin/sites', adminAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const sites = await db.query(`SELECT s.*, ac.name AS adsense_name, (SELECT COUNT(*) FROM crawl_results WHERE site_id = s.id) AS crawl_count FROM sites s LEFT JOIN adsense_configs ac ON ac.id = s.adsense_config_id ORDER BY s.id`);
|
const sites = await db.query(`SELECT s.*, ac.name AS adsense_name, (SELECT COUNT(*) FROM crawl_results WHERE site_id = s.id) AS crawl_count FROM sites s LEFT JOIN adsense_configs ac ON ac.id = s.adsense_config_id ORDER BY s.id`);
|
||||||
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
|
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
|
||||||
res.render('admin/sites', {
|
res.render('admin/sites', { ssrData: initScript({ sites: sites.rows, adsense: adsense.rows }) });
|
||||||
initialData: JSON.stringify({ sites: sites.rows, adsense: adsense.rows })
|
} catch (e) { res.render('admin/sites', { ssrData: initScript({}) }); }
|
||||||
});
|
|
||||||
} catch (e) { res.render('admin/sites', { initialData: '{}' }); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/sites/:id', adminAuth, async (req, res) => {
|
app.get('/admin/sites/:id', adminAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const site = await db.query('SELECT * FROM sites WHERE id = $1', [req.params.id]);
|
const site = await db.query('SELECT * FROM sites WHERE id = $1', [req.params.id]);
|
||||||
const results = await db.query(`SELECT id, site_id, status, error_message, crawled_at, jsonb_array_length(COALESCE(parsed_data->'items','[]'::jsonb)) AS item_count FROM crawl_results WHERE site_id = $1 ORDER BY crawled_at DESC LIMIT 20`, [req.params.id]);
|
const results = await db.query(`SELECT id, site_id, status, error_message, crawled_at, jsonb_array_length(COALESCE(parsed_data->'items','[]'::jsonb)) AS item_count FROM crawl_results WHERE site_id = $1 ORDER BY crawled_at DESC LIMIT 20`, [req.params.id]);
|
||||||
res.render('admin/site-detail', {
|
res.render('admin/site-detail', { siteId: req.params.id, ssrData: initScript({ site: site.rows[0] || {}, results: results.rows }) });
|
||||||
siteId: req.params.id,
|
} catch (e) { res.render('admin/site-detail', { siteId: req.params.id, ssrData: initScript({}) }); }
|
||||||
initialData: JSON.stringify({ site: site.rows[0] || {}, results: results.rows })
|
|
||||||
});
|
|
||||||
} catch (e) { res.render('admin/site-detail', { siteId: req.params.id, initialData: '{}' }); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/adsense', adminAuth, async (req, res) => {
|
app.get('/admin/adsense', adminAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
|
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
|
||||||
res.render('admin/adsense', { initialData: JSON.stringify(adsense.rows) });
|
res.render('admin/adsense', { ssrData: initScript(adsense.rows) });
|
||||||
} catch (e) { res.render('admin/adsense', { initialData: '[]' }); }
|
} catch (e) { res.render('admin/adsense', { ssrData: initScript([]) }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/domains', adminAuth, async (req, res) => {
|
app.get('/admin/domains', adminAuth, async (req, res) => {
|
||||||
@@ -142,15 +140,15 @@ app.get('/admin/domains', adminAuth, async (req, res) => {
|
|||||||
const domains = await db.query(`SELECT d.*, s.name AS site_name FROM domain_mappings d LEFT JOIN sites s ON s.id = d.site_id ORDER BY d.id`);
|
const domains = await db.query(`SELECT d.*, s.name AS site_name FROM domain_mappings d LEFT JOIN sites s ON s.id = d.site_id ORDER BY d.id`);
|
||||||
const sites = await db.query('SELECT id, name FROM sites ORDER BY id');
|
const sites = await db.query('SELECT id, name FROM sites ORDER BY id');
|
||||||
const adsense = await db.query('SELECT id, name FROM adsense_configs ORDER BY id');
|
const adsense = await db.query('SELECT id, name FROM adsense_configs ORDER BY id');
|
||||||
res.render('admin/domains', { initialData: JSON.stringify({ domains: domains.rows, sites: sites.rows, adsense: adsense.rows }) });
|
res.render('admin/domains', { ssrData: initScript({ domains: domains.rows, sites: sites.rows, adsense: adsense.rows }) });
|
||||||
} catch (e) { res.render('admin/domains', { initialData: '{}' }); }
|
} catch (e) { res.render('admin/domains', { ssrData: initScript({}) }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/admin/logs', adminAuth, async (req, res) => {
|
app.get('/admin/logs', adminAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const logs = await db.query(`SELECT l.*, s.name AS site_name FROM crawl_logs l LEFT JOIN sites s ON s.id = l.site_id ORDER BY l.created_at DESC LIMIT 100`);
|
const logs = await db.query(`SELECT l.*, s.name AS site_name FROM crawl_logs l LEFT JOIN sites s ON s.id = l.site_id ORDER BY l.created_at DESC LIMIT 100`);
|
||||||
res.render('admin/logs', { initialData: JSON.stringify(logs.rows) });
|
res.render('admin/logs', { ssrData: initScript(logs.rows) });
|
||||||
} catch (e) { res.render('admin/logs', { initialData: '[]' }); }
|
} catch (e) { res.render('admin/logs', { ssrData: initScript([]) }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== API =====
|
// ===== API =====
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ tr:hover td{background:rgba(255,255,255,.02)}
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
<script>var __INIT__ = <%- typeof initialData !== 'undefined' ? initialData : '{}' %>;</script>
|
<%- typeof ssrData !== 'undefined' ? ssrData : '' %>
|
||||||
<script>
|
<script>
|
||||||
function api(method, url, data) {
|
function api(method, url, data) {
|
||||||
const opts = { method, headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin' };
|
const opts = { method, headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin' };
|
||||||
|
|||||||
Reference in New Issue
Block a user