Custom Bitrix page: where the standard logic breaks
The standard Bitrix logic is designed for a predictable structure: a physical section, a component bound to it, a site template, and cache.
As soon as content stops fitting into this scheme — a composite catalog, a page with parameters in the address, outputting data from an external system — the fight with the platform begins.
A custom Bitrix page most often appears precisely here: not as a whim, but as a way to bypass the limitations that the standard tools silently impose.
The first bottleneck is Bitrix routing. The platform looks for a file along the request path, and if there is no physical file, it returns a 404 or substitutes the nearest section.
Trying to solve this via urlrewrite and rules in .htaccess works until the first composite address with several parameters: the rules proliferate, conflict with each other, and some requests go to the wrong place. The second is template overriding.
Editing header.php and footer.php for the sake of one page breaks the output on all the others, and result_modifier.php and component_epilog.php don't help when you need to change the output structure itself rather than the data inside it.
The third and most costly is output errors that aren't visible right away. The cache serves the old version of the page after edits, the component pulls data from the wrong information block, and bitrix:main.include silently returns emptiness.
On the surface the page works, but the content is wrong — and this is discovered weeks later, when the search engine has already indexed the garbage.
A separate category is performance: several nested components on one page cause a multiple increase in the number of database queries, and the response time goes beyond comfortable values.
All these failures are the consequence of a single cause: the platform's standard tools are not designed for a non-standard content structure.
Let's break down how this is solved through D7 controllers and custom routing — without editing the core and without risk to the rest of the site's pages.
What moving logic into controllers and routes gives you
When page logic is smeared across component.php, result_modifier.php, and the template, any change drags a chain of side effects along with it: an edit in one place breaks the output in another.
D7 controllers and explicit routing break that coupling — the route is responsible for which code runs, the controller is responsible for what it returns, and the template only displays the prepared data.
This is an architectural separation, not cosmetics: it directly affects response speed and how many regressions testing catches after each release.
1C-Bitrix in the current branch provides built-in mechanisms for this — routes.php with route definitions and controllers that inherit the core base class.
Previously, such a layer had to be assembled manually on top of urlrewrite.php, and it remained fragile: the rules drifted apart from the actual paths, and the handlers duplicated data fetching.
Now the route is declarative — it lives in a single file, is versioned alongside the code, and does not depend on the order in which components are included.
The practical effect is predictability of output. The controller returns a typed result rather than a mix of HTML and database queries; caching is configured at the response level rather than scattered across the template.
Because of this, page response stops "fluctuating": heavy queries move into the prepared layer, and repeated requests return a ready-made result. On high-load projects, this delivers a noticeable reduction in page generation time without rewriting the business logic.
Separately — fewer regressions. When each route is isolated, tests cover a specific handler rather than the entire page; a change in one section does not affect neighboring ones.
Regressions that previously surfaced in production a week after a release are caught at the verification stage — because the contract is visible: input, output, side effects.
Custom Page Implementation Options: Comparing Approaches
The choice of approach determines not so much the speed of launch as how the page will live on: be updated, cached, and withstand growing load. Below are three real options that teams choose between on projects, and their limitations.
| Option | When it fits | Limitations |
|---|---|---|
| Standard component template | A typical page with predictable markup, edits by a content manager | Logic is smeared across the template, hard to reuse, nowhere to write tests |
| Component approach | Reusable blocks across many pages, composite cache needed | Requires discipline: parameters, caching, and dependencies must be described explicitly |
| Moving to a controller | Non-standard logic, external sources, custom access rules | Higher barrier to entry, debugging and routing fall on the team |
Standard component template
The fastest path: the page is assembled from ready-made components, and the output is edited in the template. As long as the logic remains presentational, this is justified — the content manager changes blocks without a developer.
The problem begins when database queries and access conditions seep into the template: the code stops being reusable, and any edit drags along regressions on neighboring pages.
Component and controller
The component approach moves logic into the component itself and keeps the template thin — such a block transfers between pages and is cached predictably.
Composite cache here works correctly only with explicitly described dependencies: what invalidates the cache and on which events.
When the logic stops being typical — custom access rules, aggregation from external systems, non-standard routes — it is moved into a controller: the page gets a clean entry point, and business logic is tested separately from the markup.
How we choose
We look at three things: how many pages reuse the block, how non-standard the logic is, and who will maintain it. A typical storefront — a template is enough. Reusable blocks under load — a component and composite cache. A non-standard scenario — a controller.
Mixing approaches on one page is possible, but the boundaries should be fixed right away, otherwise maintenance turns into archaeology.
How we design and implement routing
Routing in Bitrix lives on two planes: the urlrewrite.php rules for the public-facing part and the D7 router for controllers.
While there are only a dozen pages, this goes unnoticed, but as the catalog, filters, and SEO-friendly URLs grow, the rules begin to conflict, and some requests end up in a 404 or an extra redirect.
That is why we approach the work not as "just add a rule" but as a project with fixed stages.
- Audit of current rules. We map out the existing routes, compare
urlrewrite.phpagainst the actual addresses, look for duplicates, dead rules, and redirect chains longer than one step. We record which addresses are indexed and must be preserved. - Route schema. We design the target structure: which addresses are served by a controller, which by a component, where an SEO-friendly URL is needed, and where a parameter is sufficient. We agree on the schema before writing code — changes at this stage are cheaper than rewriting after launch.
- Setting up the router and controllers. We register routes in D7, describe controllers with explicit dependencies, and move logic out of templates. We bring the SEO-friendly URL rules into a uniform form so that the parsing order does not depend on random sorting.
- Handling errors and redirects. We configure the behavior for non-existent addresses: a 404 instead of silently serving the home page, a 301 for a structure change, and correct parsing of pagination and filter parameters.
- Verification. We run the routes through automated tests and manually: response codes, redirects, SEO-friendly URLs, behavior under load. We separately verify that the old addresses have not been lost — these are direct traffic losses.
- Handover. We deliver the route schema, the configuration, the list of rules, and instructions for adding new pages, so that the client's team can extend the structure without us.
Each stage ends with an artifact: a route map, an agreed schema, a configuration, a verification report, and documentation. This way, a custom Bitrix page is added predictably, rather than by trial and error.
Output artifacts: configurations and regulations
After the project is delivered, the client receives not only working functionality, but also a complete set of materials that the support team — ours or yours — can use to develop custom pages without us. Below is a list of exactly what is handed over.
- Routing configuration — files describing the rules, the mapping of URLs to controllers, and the order of request processing. The rules are placed in a separate layer rather than smeared across templates, so a new route can be added without modifying the core.
- Route schema — a map of all custom pages: address, processing method, which components and modules are involved, and where the extension points are. It shows what will happen when any address is requested.
- Access policies — a permission matrix by roles and user groups, binding to operations on pages, and rules for guest and service sections. It separately describes how to change permissions without touching the code.
- Environment configuration — connection parameters for the database, caching, queues, and external services for development, the test environment, and the production server. Values are separated by environment, and secrets are not stored in the repository.
- Deployment regulation — the step-by-step procedure for rolling out changes, migrating structure and data, and the conditions for rolling back to a previous version. It describes which steps are automated and which require manual confirmation.
- Maintenance regulation — the frequency of platform and module updates, the procedure for working with the cache, control points after a release, and a list of typical failures and the response to them.
- Documentation — a description of the architecture of custom pages, instructions for the content manager and administrator, and comments in the code on non-obvious sections.
- Accesses and handover — accounts, repository permissions, access to the control panel and monitoring, and contacts of the responsible persons. The handover is recorded by an act so that no "dangling" accesses remain.
The set is self-sufficient: it can be used to conduct an audit, onboard a new developer, or hand the project over to another team without reconstructing the logic from the code. If you already have some of the materials, we will supplement them rather than replace them.
Case study: accelerating section rollout on custom routes
The client — an e-commerce store with a catalog of several tens of thousands of items — complained that new sections took weeks to go live, and some pages periodically returned an error under peak load.
The investigation showed that section logic was spread across components and templates, routes were assembled ad hoc, and the cache was flushed entirely on any change. We proposed moving the sections to explicit routing with D7 controllers on 1С-Bitrix.
What we did
First, we described the routes declaratively: each section got its own controller with a clear URL-matching rule, and the template was responsible only for output. The data-fetching logic was moved into a separate layer so it could be reused and covered by checks.
Then we set up separate caching by routes and tags — editing a single card no longer flushed the entire catalog.
Metrics before and after
| Metric | Before | After |
|---|---|---|
| Rollout of a new section | 2-3 weeks | 2-3 days |
| Share of pages with errors under load | noticeable | isolated cases |
| Cache flush when editing a product | entire catalog | one section |
| Catalog response time | unstable | predictable |
The key effect is predictability. Explicit routes eliminated URL conflicts, and separate caching took the load off the database during peak hours.
The client's team now adds sections using a template without touching the core, and rolls back to a previous version without downtime.
How is a D7 controller different from a standard component?
A standard component is a ready-made block of logic with a template: you connect it via $APPLICATION->IncludeComponent(), pass parameters, and get output. It lives inside a page, and routing remains with the file system.
A D7 controller is part of the core: a class with action methods, bound to a route via urlrewrite.php or the router configuration. A request arrives at a URL, the router determines the controller and action, then your code executes and the response is rendered.
The difference shows up in who controls the flow. With a component, the logic is tied to the point of inclusion in the template, while request handling, headers, response codes, and redirects remain at the page's discretion.
With a D7 controller, you control the entire cycle: parsing input data, checking permissions, forming the response, caching, and serving JSON or HTML.
This is noticeable when the page is not "a block on a site" but a standalone route: a personal account, a form with validation, receiving a webhook, or serving data to an external service.
The selection criteria are simple. If the task is a typical output of a list or a detail card and a suitable component exists, the standard path is shorter and more predictable: less code, and core updates won't break it.
If you need non-standard request logic, custom response codes, or a clean architecture without a "wall of text" in the template — go with a D7 controller.
Often the approaches are combined: the controller handles the route and data, while a component is connected inside for rendering.
Architecturally, a D7 controller is closer to how modern frameworks are structured: route, action, response.
This provides testability and logic reuse, but requires discipline — controllers, services, and templates need to be organized into layers, otherwise instead of a "wall of text" in the template you get a "wall of text" in the controller.
A standard component doesn't offer that freedom, but it doesn't require design: you connect it and it works.
Common technical concerns during customization
Before starting customization, clients most often ask the same thing: whether the custom page will "break" during the next platform update and who will maintain it when the code's author leaves. These are healthy concerns, and they are addressed not with promises, but with architecture.
The custom controller and route live in a module and are connected through the platform core's standard mechanisms, rather than by editing the platform's own files.
Therefore, a core update does not affect them: extension points remain in place, and changes in service files do not conflict with our code.
We separately pin down dependencies and library versions — during an update it is clear exactly what requires verification, rather than "everything at once."
We build maintainability into the structure: logic is moved into services, route configuration into a separate file, and templates are separated from handlers.
A new engineer can get up to speed on such code in hours, not weeks, and does not depend on who wrote the original version.
Is a custom solution compatible with future platform updates?
Yes, if it is built on standard extension points rather than core modifications. We do not touch the platform's service files, so the update proceeds normally.
Before a major update, we run a regression across the affected routes and controllers — it is clear in advance what requires adaptation.
What happens if the contractor who wrote the code leaves?
The code is handed over with documentation: the route schema, the purpose of services, dependencies, and the deployment procedure. The logic is isolated in a module, so it can be maintained and developed without the author. During handover, we conduct a walkthrough with the client's team or a new contractor.
Can an unsuccessful update be rolled back?
Yes. Configuration and code are versioned, and deployment is reproducible. If something does not work correctly after an update, we revert to the previous state and investigate the cause on a test environment without touching the production site.
How do we verify that the customization has not broken existing pages?
We run a regression across the affected routes and key scenarios: catalog, cart, account area, forms. We test custom pages separately — and in combination with the other sections, to make sure the routing does not intercept other addresses.
Let's discuss your task and come back with a plan
The discussion doesn't start with a ten-page brief, but with an analysis of what already exists.
You send access to the repository or an export of the project, a description of the current logic of the sections, and what isn't working: slow catalog output, manual edits in templates, the inability to add a new page type without modifying the core. That's enough to understand the architecture and estimate the scope of work.
Next, we break the task down layer by layer: which routes are already taken, where the business logic sits, what can be reused, and what will have to be moved into separate controllers.
Based on the results of the analysis, you receive a preliminary plan — without abstract wording, with concrete steps and the order of work.
- Analysis of the current project: template structure, components, customization points, bottlenecks in routing.
- Scope agreement: which pages we build, what stays on standard tools, where a custom controller is needed.
- Timeline estimate by stages: route design, development, integration with the existing template, testing on a copy.
- Migration plan: how we transfer current URLs without losing rankings and creating broken links, what we configure in the URL handling rules.
- Rollback scheme: preserving the working version, deployment order, return points at each step.
- Support policy: how we make edits after launch, who responds to incidents and within what timeframes.
- Agreement on responsible parties on both sides and the reporting format by stages.
What the preliminary plan includes: a list of pages with the implementation type, a route scheme and mapping to sections, the set of controllers and their areas of responsibility, a list of affected components, stages with approximate timelines and acceptance criteria for each.
As a separate item — risks: conflicts with platform updates, dependencies on third-party modules, high-load areas.
The plan is refined after the first stage — when we see the code, not the description. If limitations come up during the analysis, we flag them immediately, not halfway through development.
Describe the task in whatever form is convenient — we'll come back with an estimate, timelines, and a plan for the next steps.

