국내 스타트업들이 노션으로 된 웹사이트들을 많이 활용하면서 웹사이트 구축 뿐만 아니라 협업 툴로 유망한 노션(Notion)의 다양한 활용도가 주목 받고 있는데요. 그 중에서 꽤나 주목할만한 것은 바로 노션 페이지를 웹사이트로 바로 써먹을 수 있다는 점입니다.
노션 웹사이트 생성하기
노션 계정이 없으신 분들은 계정을 먼저 만들어 주세요. 이미 계정이 있다면 로그인 해주시면 됩니다. 이후 페이지를 생성해주시면 되는데요. 워크스페이스, 프라이빗 모두 가능하지만 가능한한 홈페이지 형태로 만들고 싶다면 디렉토리 구조를 잘 지키는게 좋습니다. 템플릿을 선택하는 것도 좋은 방법이에요.
노션 페이지는 템플릿 복제가 허용되어 있는 경우 정말 간단하게 템플릿을 복사&붙여넣기 할 수 있습니다. 다음은 노션 페이지 무료 템플릿의 예시 입니다.
비즈니스 플래너 템플릿
https://www.notion.so/4c147b42428d42fa881b75ffec1ffb44?v=c59300addd02468ebcecb58719579bd1
심플 링크 템플릿
https://simple-ink.notion.site/simple-ink/Hey-there-49c55bfde04c463ead2e34ff32c0d37d
Job Board 링크 템플릿
https://www.notion.so/Job-Board-3027546793854cd997734b324c64c921
이 외에도 노션 페이지 안에서 다양한 조건과 디자인을 활용할 수 있습니다. 컨텐츠를 다 채워 넣고 나면 다음은 바로 도메인 연결 인데요. 도메인을 구매하셨다면 의외로 쉬운 방법으로 노션 홈페이지 상단 주소에 자신만의 도메인 주소를 반영할 수 있습니다.
노션 홈페이지 커스텀 도메인의 장점?
노션 페이지의 주소체계는 notion.so/pgae주소로 나열 되다 보니 SEO를 비롯해 사용자 경험이 썩 좋다고 말하기 어렵습니다. 게다가 아무래도 홈페이지가 아니다 보니 신뢰도를 주는데에도 제한사항이 생길 수가 있는데요. 그러한 부분들을 어느정도 해소해주면서 별도의 호스팅 비용이 나가지 않는 방법으로는 ‘클라우드 플레어’를 이용한 도메인 연동을 추천 드립니다.
우피(oopy)라는 서비스에서 월 고정요금을 내고 호스팅도 가능하지만 별로 추천드리고 싶진 않습니다~!
노션 홈페이지와 자신만의 도메인 연결하는 방법 [도메인 구입비 외 무료!]
먼저 노션 우측 상단의 share > publish 를 통해서 페이지를 누구나 접근 가능한 상태로 만들어 주셔야 합니다.
퍼블리싱하고 나면 외부 노출이 가능한 페이지 주소가 제공이 되는데 이 페이지 주소가 중요합니다!
- 클라우드 플레어 계정 만들기 (혹은 로그인)
https://dash.cloudflare.com/sign-up 로 접속해서 클라우드 플레어 계정을 만들어 줍니다. - 연결하실 도메인 이름을 입력하고 사이트를 추가 합니다.
- 프리 플랜 선택
무료 플랜을 선택하고 confirm plan 버튼 클릭 - 네임 서버를 클라우드 플레어로 설정해주어야 합니다.
.ns.cloudflare.com 으로 시작하는 값 2개를 찾아주세요! 클라우드 플레어 페이지의 하단으로 쭉 넘겨보시면 나옵니다.
예를 들어서 후이즈 도메인에서 도메인을 구입했다면 해당 홈페이지 관리화면에서 네임서버 변경을 통해 클라우드 플레어로 권한을 넘겨 줍니다. - Workers page 를 생성 합니다.
다음으로 계속 넘어가 주시면 스크립트 생성 화면이 나오게 되는데요. 해당 스크립트를 이제 환경에 맞게 바꿔 주셔야 합니다.
클라우드 플레어 무료 연결 코드 생성기
클라우드 플레어가 우리가 설정한 도메인으로 리다이렉트 처리를 하기 위해서는 알맞은 소스코드 수정이 필요합니다. 이런 코딩을 자동으로 해줄 친구가 바로 이 녀석인데요. 아래 링크를 눌러 접속해 주시기 바랍니다.
Step 2 부분을 열고 도메인과, 노션 링크를 추가한 뒤 코드를 카피합니다. 다음은 코드 예시 에요.
/* CONFIGURATION STARTS HERE */
/* Step 1: enter your domain name like fruitionsite.com */
const MY_DOMAIN = 'example.com';
/*
* Step 2: enter your URL slug to page ID mapping
* The key on the left is the slug (without the slash)
* The value on the right is the Notion page ID
*/
const SLUG_TO_PAGE = {
'': '123823b5661a4522aec9f8ad2e76f1c9',
};
/* Step 3: enter your page title and description for SEO purposes */
const PAGE_TITLE = '';
const PAGE_DESCRIPTION = '';
/* Step 4: enter a Google Font name, you can choose from https://fonts.google.com */
const GOOGLE_FONT = '';
/* Step 5: enter any custom scripts you'd like */
const CUSTOM_SCRIPT = ``;
/* CONFIGURATION ENDS HERE */
const PAGE_TO_SLUG = {};
const slugs = [];
const pages = [];
Object.keys(SLUG_TO_PAGE).forEach(slug => {
const page = SLUG_TO_PAGE[slug];
slugs.push(slug);
pages.push(page);
PAGE_TO_SLUG[page] = slug;
});
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request));
});
function generateSitemap() {
let sitemap = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
slugs.forEach(
(slug) =>
(sitemap +=
'<url><loc>https://' + MY_DOMAIN + '/' + slug + '</loc></url>')
);
sitemap += '</urlset>';
return sitemap;
}
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
function handleOptions(request) {
if (request.headers.get('Origin') !== null &&
request.headers.get('Access-Control-Request-Method') !== null &&
request.headers.get('Access-Control-Request-Headers') !== null) {
// Handle CORS pre-flight request.
return new Response(null, {
headers: corsHeaders
});
} else {
// Handle standard OPTIONS request.
return new Response(null, {
headers: {
'Allow': 'GET, HEAD, POST, PUT, OPTIONS',
}
});
}
}
async function fetchAndApply(request) {
if (request.method === 'OPTIONS') {
return handleOptions(request);
}
let url = new URL(request.url);
url.hostname = 'www.notion.so';
if (url.pathname === '/robots.txt') {
return new Response('Sitemap: https://' + MY_DOMAIN + '/sitemap.xml');
}
if (url.pathname === '/sitemap.xml') {
let response = new Response(generateSitemap());
response.headers.set('content-type', 'application/xml');
return response;
}
let response;
if (url.pathname.startsWith('/app') && url.pathname.endsWith('js')) {
response = await fetch(url.toString());
let body = await response.text();
response = new Response(body.replace(/www.notion.so/g, MY_DOMAIN).replace(/notion.so/g, MY_DOMAIN), response);
response.headers.set('Content-Type', 'application/x-javascript');
return response;
} else if ((url.pathname.startsWith('/api'))) {
// Forward API
response = await fetch(url.toString(), {
body: url.pathname.startsWith('/api/v3/getPublicPageData') ? null : request.body,
headers: {
'content-type': 'application/json;charset=UTF-8',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'
},
method: 'POST',
});
response = new Response(response.body, response);
response.headers.set('Access-Control-Allow-Origin', '*');
return response;
} else if (slugs.indexOf(url.pathname.slice(1)) > -1) {
const pageId = SLUG_TO_PAGE[url.pathname.slice(1)];
return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 301);
} else {
response = await fetch(url.toString(), {
body: request.body,
headers: request.headers,
method: request.method,
});
response = new Response(response.body, response);
response.headers.delete('Content-Security-Policy');
response.headers.delete('X-Content-Security-Policy');
}
return appendJavascript(response, SLUG_TO_PAGE);
}
class MetaRewriter {
element(element) {
if (PAGE_TITLE !== '') {
if (element.getAttribute('property') === 'og:title'
|| element.getAttribute('name') === 'twitter:title') {
element.setAttribute('content', PAGE_TITLE);
}
if (element.tagName === 'title') {
element.setInnerContent(PAGE_TITLE);
}
}
if (PAGE_DESCRIPTION !== '') {
if (element.getAttribute('name') === 'description'
|| element.getAttribute('property') === 'og:description'
|| element.getAttribute('name') === 'twitter:description') {
element.setAttribute('content', PAGE_DESCRIPTION);
}
}
if (element.getAttribute('property') === 'og:url'
|| element.getAttribute('name') === 'twitter:url') {
element.setAttribute('content', MY_DOMAIN);
}
if (element.getAttribute('name') === 'apple-itunes-app') {
element.remove();
}
}
}
class HeadRewriter {
element(element) {
if (GOOGLE_FONT !== '') {
element.append(`<link href="https://fonts.googleapis.com/css?family=${GOOGLE_FONT.replace(' ', '+')}:Regular,Bold,Italic&display=swap" rel="stylesheet">
<style>* { font-family: "${GOOGLE_FONT}" !important; }</style>`, {
html: true
});
}
element.append(`<style>
div.notion-topbar > div > div:nth-child(3) { display: none !important; }
div.notion-topbar > div > div:nth-child(4) { display: none !important; }
div.notion-topbar > div > div:nth-child(5) { display: none !important; }
div.notion-topbar > div > div:nth-child(6) { display: none !important; }
div.notion-topbar-mobile > div:nth-child(3) { display: none !important; }
div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; }
div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; }
</style>`, {
html: true
})
}
}
class BodyRewriter {
constructor(SLUG_TO_PAGE) {
this.SLUG_TO_PAGE = SLUG_TO_PAGE;
}
element(element) {
element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
<script>
window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}';
const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
const PAGE_TO_SLUG = {};
const slugs = [];
const pages = [];
const el = document.createElement('div');
let redirected = false;
Object.keys(SLUG_TO_PAGE).forEach(slug => {
const page = SLUG_TO_PAGE[slug];
slugs.push(slug);
pages.push(page);
PAGE_TO_SLUG[page] = slug;
});
function getPage() {
return location.pathname.slice(-32);
}
function getSlug() {
return location.pathname.slice(1);
}
function updateSlug() {
const slug = PAGE_TO_SLUG[getPage()];
if (slug != null) {
history.replaceState(history.state, '', '/' + slug);
}
}
function onDark() {
el.innerHTML = '<div title="Change to Light Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgb(46, 170, 220); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(12px) translateY(0px);"></div></div></div></div>';
document.body.classList.add('dark');
__console.environment.ThemeStore.setState({ mode: 'dark' });
};
function onLight() {
el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgba(135, 131, 120, 0.3); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(0px) translateY(0px);"></div></div></div></div>';
document.body.classList.remove('dark');
__console.environment.ThemeStore.setState({ mode: 'light' });
}
function toggle() {
if (document.body.classList.contains('dark')) {
onLight();
} else {
onDark();
}
}
function addDarkModeButton(device) {
const nav = device === 'web' ? document.querySelector('.notion-topbar').firstChild : document.querySelector('.notion-topbar-mobile');
el.className = 'toggle-mode';
el.addEventListener('click', toggle);
nav.appendChild(el);
onLight();
}
const observer = new MutationObserver(function() {
if (redirected) return;
const nav = document.querySelector('.notion-topbar');
const mobileNav = document.querySelector('.notion-topbar-mobile');
if (nav && nav.firstChild && nav.firstChild.firstChild
|| mobileNav && mobileNav.firstChild) {
redirected = true;
updateSlug();
addDarkModeButton(nav ? 'web' : 'mobile');
const onpopstate = window.onpopstate;
window.onpopstate = function() {
if (slugs.includes(getSlug())) {
const page = SLUG_TO_PAGE[getSlug()];
if (page) {
history.replaceState(history.state, 'bypass', '/' + page);
}
}
onpopstate.apply(this, [].slice.call(arguments));
updateSlug();
};
}
});
observer.observe(document.querySelector('#notion-app'), {
childList: true,
subtree: true,
});
const replaceState = window.history.replaceState;
window.history.replaceState = function(state) {
if (arguments[1] !== 'bypass' && slugs.includes(getSlug())) return;
return replaceState.apply(window.history, arguments);
};
const pushState = window.history.pushState;
window.history.pushState = function(state) {
const dest = new URL(location.protocol + location.host + arguments[2]);
const id = dest.pathname.slice(-32);
if (pages.includes(id)) {
arguments[2] = '/' + PAGE_TO_SLUG[id];
}
return pushState.apply(window.history, arguments);
};
const open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function() {
arguments[1] = arguments[1].replace('${MY_DOMAIN}', 'www.notion.so');
return open.apply(this, [].slice.call(arguments));
};
</script>${CUSTOM_SCRIPT}`, {
html: true
});
}
}
async function appendJavascript(res, SLUG_TO_PAGE) {
return new HTMLRewriter()
.on('title', new MetaRewriter())
.on('meta', new MetaRewriter())
.on('head', new HeadRewriter())
.on('body', new BodyRewriter(SLUG_TO_PAGE))
.transform(res);
}
상단에 보이는
/* Step 3: enter your page title and description for SEO purposes */
const PAGE_TITLE = '';
- 노션 홈페이지의 검색엔진에 노출 될 제목을 입력해주세요 -
const PAGE_DESCRIPTION = '';
- 노션 홈페이지의 검색엔진에 노출 될 설명 문구를 120자 내외로 입력해주세요 -
/* Step 4: enter a Google Font name, you can choose from https://fonts.google.com */
const GOOGLE_FONT = '';
/* Step 5: enter any custom scripts you'd like */
const CUSTOM_SCRIPT = ``;
영역을 채워넣으면 SEO와 스크립트 처리까지 추가로 할 수 있기 때문에 꼭! 반영하고 넘어가시길 추천 드립니다.
클라우드 플레어에 해당 코드를 복붙 완료하면 그 이후부터는 테스트만 진행하면 되기 때문에 매우 간단하죠?