One year ago, Proga Tech decided to build its own website instead of outsourcing it. Here is how Notion became our CMS, applicant tracker and form backend and what we'd warn you about.
Every growing team hits the same wall. The simple website that once did the job starts to feel like a business card in a world that wants a story. You have events worth remembering, projects worth showing off, partners worth celebrating, and a team whose personality doesn’t fit into a template. And you want to act on ideas quickly: a new event page this afternoon, a seasonal touch next week, a partner landing page by Friday.
Then comes the real question: who builds it? Hand it to an agency and you’ll get something polished, but every small change becomes a ticket, a quote, and a wait. Build it yourselves and you keep control, but you take on the work and the risk.
We faced exactly that choice last year. And at the center of the whole system is a tool we already used every day: Notion.
Why not WordPress?
WordPress is a reasonable choice for many companies, and we considered it seriously. For us, three things tipped the balance:
- Ownership. With an outsourced WordPress build, meaningful changes go through the people who built it. We wanted design and content decisions to stay inside the team.
- Yet another tool. WordPress would have been one more admin panel, one more login, and one more place where content lives. Our drafts, hiring notes and plans were already in Notion.
- Beyond the blog. We needed job listings, applications, contact requests and notifications. On WordPress, that usually means a stack of plugins to install, update and secure. And on Notion, we got this already included by default.
A custom Next.js frontend with Notion behind it gave us full control over design and performance, with a publishing workflow the team already knew.
Why Notion made sense for us
For us, Notion was not “just a cheaper CMS.” It was the place where the team already thought and wrote.
That matters more than it seems. A good internal tool is less about features and more about adoption. If publishing happens in the same place where work is drafted, reviewed and discussed, there is no new process to learn.
Notion gave us:
- structured databases for posts, vacancies, applications and contacts;
- flexible pages for rich content;
- statuses, filters, relations, views and comments;
- native automations for Slack and email notifications;
- file uploads for CVs and attachments;
- a familiar workspace for non-engineering teammates.
On the engineering side, we paired Notion with Next.js. The frontend is static where static makes sense, cached aggressively and regenerated when content changes. Visitors never wait on Notion.
The architecture

We use Notion in two main ways:
- As a content source for pages such as blog posts, vacancies, and landing pages.
- As an operational database for submissions such as contact requests and job applications.
For reads, we use Notion page content to render rich website pages. For writes, we use the official Notion API to create database entries from website forms.
The split is practical. Content pages need rich block rendering: headings, callouts, images, code blocks, embeds, and structured page content. Form submissions need stable, predictable writes into a database.
A note on the two Notion APIs
We use the official Notion API for structured data and writes. With the
2025-09-03 API version, queries target a data source ID, not the database ID used in older examples. The integration needs access to each database it uses, including related databases for application-to-vacancy relations. Its token remains on the server.In practice, setup means creating an internal Notion integration, sharing the content and submission databases with it, storing the token and data source IDs as server-side environment variables, and confirming that the publishing status and slug properties match the frontend queries. With the newer API, you can retrieve a database to find its data source ID or store that ID directly after setup.
For the rich body of an article or vacancy,
notion-client provides the recordMap expected by react-notion-x. It uses Notion’s private API, so we treat that as a dependency to monitor. The official API can also retrieve blocks, but would require recursive retrieval and a different rendering layer. Pages read without an authenticated Notion session must be deliberately made accessible to that client, consistent with our publishing rules.Use case 1: Blog posts
Our blog is managed from a Notion database. Each row is one post, and the body of the post lives inside the Notion page attached to that row.

For the blog, we keep the title, URL slug, short summary, publication status, cover, author, and optional SEO metadata in the row. The article itself lives in the page.
The flow for writers is straightforward:
- Create a new row in the Blog database.
- Write the article directly inside the Notion page.
- Fill in the title, slug, summary, cover image, author, and SEO fields.
- Mark the post as published.
- The website picks it up automatically after cache revalidation.
No deployment. No pull request. No external admin panel.
On the engineering side, we query the database for metadata and fetch the page content for rendering.
The
extractPost function maps Notion’s raw property structure into a clean typed object for the frontend. That keeps the UI simple and protects it from Notion-specific implementation details.For rendering rich content, we use a Notion renderer in the frontend:
This lets the team use native Notion formatting — headings, callouts, toggles, images, code snippets, embeds — without asking developers to create a new block type every time.
Use case 2: Landing pages and brand experiments
This is where the decision became especially valuable for us.
A website is also a living brand surface: event pages, campaign pages, greetings, announcements, partner collaborations. Because the website is built in Next.js and powered by flexible content from Notion, we are not locked into one rigid template.
When a new event is coming, we can create a dedicated page with a custom visual direction, include partner styling, and still keep our own signature pink. When we want to add anniversary greetings or seasonal decorations, we do not need to wait for an external team to understand the mood, the timing, or the small brand details that matter to us.
The team can move from idea to published page without translating every decision through a vendor. That gives people a stronger sense of ownership over the brand and the company direction.
Use case 3: Careers and job listings
The careers section follows the same pattern as the blog.
A Vacancies database stores open roles. Each vacancy is a Notion page with a rich job description, requirements, benefits, and application details. The hiring team edits the role in Notion, marks it as published, and the website updates automatically.
Each vacancy has a title, location, employment details, a short preview, and a publication status. The full description lives in its Notion page.
A simplified query looks like this:
🧩 The business value is clear: no separate job board CMS, no custom HR admin panel, no extra process for the hiring team. Vacancies live where the team already collaborates.
Use case 4: Contact forms and job applications


When someone submits a contact form, our Next.js API route validates the request and creates a new page in the Contacts database. The team can then manage the request directly inside Notion: add comments, assign follow-up, update status, and keep the full history in one place.
The flow looks like this:
A simplified create call looks like this:
The route also needs to protect a public form: validate fields, limit submissions, and use a honeypot or CAPTCHA as appropriate. Notion rich-text items have a length limit, so
toRichText splits longer messages into valid segments. If a form includes a file, upload it to Notion first and attach the resulting file-upload ID when creating the page. Check file size and type before upload and attach it before the upload expires. The server-side flow is: create a Notion file upload, send the file bytes, then include { name, type: "file_upload", file_upload: { id } } in the page’s file property. This keeps CVs attached to applications without a separate storage service; limits and availability depend on the workspace plan.For job applications, we use the same idea with a richer record: the candidate’s contact details, related vacancy, CV, submission date, and pipeline status. The form sends the vacancy’s page ID for the relation, and the integration must have access to the Vacancies database.
This gives us a lightweight applicant tracking system without introducing another platform. The hiring team can use board views, filters, comments, and statuses directly in Notion.
It is not a replacement for a large enterprise ATS. It is exactly what we need at our scale: simple, visible, and owned by the team.
Automations without another automation platform
One of the nicest parts of this setup is that many automations can happen inside Notion itself.
When a new page is created in a database, Notion can trigger a Slack message or an email, depending on the workspace plan and connected accounts. We use this for things like:
- alerting the team when a new contact request arrives;
- notifying hiring when a candidate applies;
- keeping the team updated when a vacancy or submission changes status.
That means we did not need to build and maintain a separate notification layer for every workflow. The automation lives next to the data, which makes it easier for the team to understand and adjust. A typical rule is
- Page added → Send Slack notification for new contact requests
- Page added → Send mail to hiring for new applications
- Status-change alerts for vacancies and candidates
- Page added → Send the candidate an automatic confirmation that we’ve received their application
Performance and SEO: why static is not a problem
A common concern with using Notion as a CMS is performance. If the website fetched content from Notion on every request, that concern would be valid.
That is not how we built it.
The website uses Next.js with static generation and cache revalidation. Pages are generated, cached, and served quickly. Notion is the source of content, but it is not sitting in the critical path for every visitor.
For example, a blog page can revalidate on a schedule:
For most content, this is more than enough. A blog post, vacancy, or event page does not need to update every second. When we need faster propagation, a Notion automation can call a protected Next.js endpoint that revalidates the affected path. The endpoint should verify a shared secret. We also have to account for expiring Notion-hosted image URLs: cached pages should refresh asset URLs before they expire or serve assets through durable storage.
This makes the website fast, reliable, and SEO-friendly while preserving a simple publishing workflow for the team.
Static is not a limitation here. It is part of the strategy.
What we saved and what we gained
The obvious savings are financial. We avoided a stack of tools for CMS, forms, hiring, and automations. Since we already used Notion, the website did not introduce a new subscription-heavy operating model.
Cost comparison
Need | Illustrative separate-tool budget | Our setup |
Website platform and hosting | WordPress, ~$25–70/month | $0, hosted for free* |
Blog and content | Headless CMS, ~$300/month | Included in the Notion workspace |
Job listings and applications | ATS, ~$200/month | Notion vacancies and applications |
Contact forms | Form tool, ~$50/month | Custom Next.js API route |
Slack and email alerts | Automation tool, ~$50/month | Notion automations (depends on the Notion plan) |
Total in this example | ~$600/month | No separate subscriptions for these tools |
💸 Our current traffic fits within a free hosting setup, so this line costs us nothing today. Larger sites should budget for paid hosting.
These are rough example amounts to illustrate the potential cost of a separate stack, not current vendor quotes or a measured Proga Tech saving. Actual prices depend on plans, seats, and usage. Notion itself may require a paid plan, and custom API routes take engineering time to build and maintain.
The bigger saving is in coordination. We don't explain our values to an outside team every time we want something small but meaningful, and decisions stay with the people who care about the brand the most.
Limitations we respect
This setup works very well for us, but it is not the right answer for every product.
There are a few limitations worth being honest about:
- API limits matter. You should not use Notion as a live database for every page request. Cache aggressively and revalidate intentionally.
- Unofficial rendering clients carry risk. If you rely on internal Notion APIs for rich page rendering, keep dependencies pinned and monitor changes.
- It is not built for massive content operations. If you run a large media platform, high-volume marketplace, or real-time catalog, you probably need a purpose-built CMS or database.
- Files and public forms need care. Notion-hosted file URLs expire, uploads have size and time limits, and public routes need validation and abuse protection.
- Publishing delay. Edits to page bodies appear only after scheduled revalidation, unless someone triggers a republish.
☝🏻 Notion should not replace every CMS. But for teams that already live in Notion, it can be the smartest operational layer they have.
The result
After a year of using this setup, the strongest signal is simple: the team loves it.
The website feels like Proga Tech because it is shaped by Proga Tech. We publish content, launch event pages, run hiring and handle incoming requests from the same place we plan our work, while visitors get a fast, static, SEO-friendly site.
That combination is exactly what we wanted.
It also became a good example of how we think about technology for clients.
A smart solution is not always the biggest platform or the most expensive implementation. Sometimes the smartest solution is to look at the tools a team already uses, understand the business workflow, and build the missing bridge between operations and software.
That is what we did for ourselves.
And that is the kind of solution we like building for others: practical, technically solid, cost-conscious, and deeply connected to how the team actually works.


